|
1 | 1 | import * as React from 'react'; |
2 | | -import { Bullseye, Button, Spinner } from '@patternfly/react-core'; |
| 2 | +import { Bullseye } from '@patternfly/react-core'; |
3 | 3 | import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; |
4 | | -import MinusCircleIcon from '@patternfly/react-icons/dist/js/icons/minus-circle-icon'; |
5 | 4 |
|
6 | 5 | import { DeviceApplicationStatus } from '@flightctl/types'; |
7 | 6 | import { useTranslation } from '../../../hooks/useTranslation'; |
8 | 7 | import ApplicationStatus from '../../Status/ApplicationStatus'; |
9 | | -import WithTooltip from '../../common/WithTooltip'; |
10 | | - |
11 | | -import './ApplicationsTable.css'; |
12 | 8 |
|
13 | 9 | type ApplicationsTableProps = { |
14 | | - // Contains the statuses of all detected applications and systemdUnits |
| 10 | + // Contains the statuses of all detected applications |
15 | 11 | appsStatus: DeviceApplicationStatus[]; |
16 | 12 | // List of apps as defined the device / fleet spec |
17 | 13 | specApps: string[]; |
18 | | - // List of systemd units as defined in the device / fleet spec |
19 | | - specSystemdUnits: string[]; |
20 | | - // Map: (systemdUnitName, timeItWasAdded) |
21 | | - addedSystemdUnitDates: Record<string, number>; |
22 | | - onSystemdDelete?: (deletedUnit: string) => void; |
23 | | - isUpdating: boolean; |
24 | | - canEdit: boolean; |
25 | 14 | }; |
26 | 15 |
|
27 | | -const DELETE_SYSTED_TIMEOUT = 30000; // 30 seconds |
28 | | - |
29 | | -const ApplicationsTable = ({ |
30 | | - appsStatus, |
31 | | - specApps, |
32 | | - specSystemdUnits, |
33 | | - addedSystemdUnitDates, |
34 | | - onSystemdDelete, |
35 | | - isUpdating, |
36 | | - canEdit, |
37 | | -}: ApplicationsTableProps) => { |
| 16 | +const ApplicationsTable = ({ appsStatus, specApps }: ApplicationsTableProps) => { |
38 | 17 | const { t } = useTranslation(); |
39 | 18 |
|
40 | | - // Required to be able to detect removed systemd units for their correct type. |
41 | | - // It takes a bit for them to be removed from the applications list. |
42 | | - const [deletedSystemdUnits, setDeletedSystemdUnits] = React.useState<string[]>([]); |
43 | | - |
44 | | - React.useEffect(() => { |
45 | | - // Remove a service from the deleted list if it was added back later |
46 | | - const filtered = deletedSystemdUnits.filter((deletedUnit) => { |
47 | | - if (addedSystemdUnitDates[deletedUnit]) { |
48 | | - return false; |
49 | | - } |
50 | | - return true; |
51 | | - }); |
52 | | - if (filtered.length < deletedSystemdUnits.length) { |
53 | | - setDeletedSystemdUnits(filtered); |
54 | | - } |
55 | | - // eslint-disable-next-line react-hooks/exhaustive-deps |
56 | | - }, [addedSystemdUnitDates]); |
57 | | - |
58 | | - const appsAndSystemdUnits: string[] = []; |
| 19 | + // Includes applications already reported in status as well as those that are only in the spec yet |
| 20 | + const allAppNames: string[] = []; |
59 | 21 | specApps.forEach((app) => { |
60 | | - appsAndSystemdUnits.push(app); |
61 | | - }); |
62 | | - specSystemdUnits.forEach((systemdUnit) => { |
63 | | - appsAndSystemdUnits.push(systemdUnit); |
| 22 | + allAppNames.push(app); |
64 | 23 | }); |
65 | 24 | appsStatus.forEach((appStatus) => { |
66 | | - if (!appsAndSystemdUnits.includes(appStatus.name)) { |
67 | | - appsAndSystemdUnits.push(appStatus.name); |
| 25 | + if (!allAppNames.includes(appStatus.name)) { |
| 26 | + allAppNames.push(appStatus.name); |
68 | 27 | } |
69 | 28 | }); |
70 | 29 |
|
71 | | - return appsAndSystemdUnits.length ? ( |
| 30 | + return allAppNames.length ? ( |
72 | 31 | <Table aria-label={t('Device applications table')}> |
73 | 32 | <Thead> |
74 | 33 | <Tr> |
75 | 34 | <Th>{t('Name')}</Th> |
76 | 35 | <Th modifier="wrap">{t('Status')}</Th> |
77 | 36 | <Th modifier="wrap">{t('Ready')}</Th> |
78 | 37 | <Th modifier="wrap">{t('Restarts')}</Th> |
79 | | - <Th modifier="wrap">{t('Type')}</Th> |
80 | 38 | </Tr> |
81 | 39 | </Thead> |
82 | 40 | <Tbody> |
83 | | - {appsAndSystemdUnits.map((appName) => { |
84 | | - const appDetails = appsStatus.find((app) => app.name === appName); |
85 | | - const isDeletedSystemdUnit = deletedSystemdUnits.includes(appName); |
86 | | - const isAddedSystemdUnit = !!addedSystemdUnitDates[appName]; |
87 | | - const isApp = |
88 | | - specApps.includes(appName) || |
89 | | - !(specSystemdUnits.includes(appName) || isDeletedSystemdUnit || isAddedSystemdUnit); |
90 | | - |
91 | | - const deleteSystemdUnit = canEdit && !isDeletedSystemdUnit && onSystemdDelete && ( |
92 | | - <Button |
93 | | - aria-label={t('Delete system service')} |
94 | | - isDisabled={isUpdating} |
95 | | - variant="plain" |
96 | | - icon={<MinusCircleIcon />} |
97 | | - onClick={() => { |
98 | | - setDeletedSystemdUnits(deletedSystemdUnits.concat(appName)); |
99 | | - onSystemdDelete(appName); |
100 | | - }} |
101 | | - /> |
102 | | - ); |
103 | | - |
104 | | - if (!appDetails) { |
105 | | - // It's an app or a systemd unit which has not been reported yet |
106 | | - const appAddedTime = addedSystemdUnitDates[appName] || 0; |
107 | | - // For apps there is no spinner since we don't know when the app was added to the spec |
108 | | - const showSpinner = !isApp && Date.now() - appAddedTime < DELETE_SYSTED_TIMEOUT; |
109 | | - return ( |
110 | | - <Tr key={appName} className="applications-table__row"> |
111 | | - <Td dataLabel={t('Name')}>{appName}</Td> |
112 | | - |
113 | | - <Td dataLabel={t('Status')} colSpan={showSpinner ? 3 : 1}> |
114 | | - {showSpinner ? ( |
115 | | - <> |
116 | | - <Spinner size="sm" /> {t('Waiting for service to be reported...')} |
117 | | - </> |
118 | | - ) : ( |
119 | | - '-' |
120 | | - )} |
121 | | - </Td> |
122 | | - {!showSpinner && <Td dataLabel={t('Ready')}>-</Td>} |
123 | | - {!showSpinner && <Td dataLabel={t('Restarts')}>-</Td>} |
124 | | - <Td dataLabel={t('Type')}> |
125 | | - {isApp ? ( |
126 | | - <>{t('App')}</> |
127 | | - ) : ( |
128 | | - <> |
129 | | - {t('Systemd')} {deleteSystemdUnit} |
130 | | - </> |
131 | | - )} |
132 | | - </Td> |
133 | | - </Tr> |
134 | | - ); |
135 | | - } |
136 | | - |
137 | | - let typeColumnContent: React.ReactNode; |
138 | | - |
139 | | - if (isApp) { |
140 | | - typeColumnContent = t('App'); |
141 | | - } else if (onSystemdDelete) { |
142 | | - let extraContent: React.ReactNode; |
143 | | - if (isDeletedSystemdUnit) { |
144 | | - extraContent = ( |
145 | | - <WithTooltip |
146 | | - showTooltip |
147 | | - content={t('{{ appName }} is being removed, this may take some time.', { appName })} |
148 | | - > |
149 | | - <Spinner size="sm" /> |
150 | | - </WithTooltip> |
151 | | - ); |
152 | | - } else { |
153 | | - extraContent = deleteSystemdUnit; |
154 | | - } |
155 | | - |
156 | | - typeColumnContent = ( |
157 | | - <> |
158 | | - {t('Systemd')} {extraContent} |
159 | | - </> |
160 | | - ); |
161 | | - } else { |
162 | | - typeColumnContent = t('Systemd'); |
163 | | - } |
| 41 | + {allAppNames.map((appName) => { |
| 42 | + const appDetails = appsStatus.find((app) => app.name === appName) || { |
| 43 | + status: null, |
| 44 | + ready: '-', |
| 45 | + restarts: '-', |
| 46 | + }; |
164 | 47 |
|
165 | 48 | return ( |
166 | | - <Tr key={appName} className="fctl-applications-table__row"> |
| 49 | + <Tr key={appName}> |
167 | 50 | <Td dataLabel={t('Name')}>{appName}</Td> |
168 | 51 | <Td dataLabel={t('Status')}> |
169 | | - <ApplicationStatus status={appDetails.status} /> |
| 52 | + {appDetails.status ? <ApplicationStatus status={appDetails.status} /> : '-'} |
170 | 53 | </Td> |
171 | 54 | <Td dataLabel={t('Ready')}>{appDetails.ready}</Td> |
172 | 55 | <Td dataLabel={t('Restarts')}>{appDetails.restarts}</Td> |
173 | | - <Td dataLabel={t('Type')}>{typeColumnContent}</Td> |
174 | 56 | </Tr> |
175 | 57 | ); |
176 | 58 | })} |
|
0 commit comments