Skip to content

Commit 0f3c7b1

Browse files
authored
refactor(home): migrate view to react [EE-1810] (portainer#6314)
* refactor(http): parse axios errors (portainer#6325) * refactor(home): use endpoint-list as react component [EE-1814] (portainer#6060) * refactor(home): use endpoint-list as react component fix(home): add missing features and refactors - kubebutton - group name - poll when endpoint is off - state management refactor(endpoints): use stat component fix(endpoints): add space between items refactor(endpoints): move stats to components refactor(endpoints): fetch time refactor(home): move logic refactor(home): move fe render logic refactor(settings): use vanilla js for publicSettings refactor(kube): remove angular from kube config service feat(home): add kubeconfig button feat(home): send analytics when opening kubeconfig modal fix(home): memoize footer refactor(home): use react-query for loading fix(home): show correct control for kubeconfig modal refactor(home): use debounce refactor(home): use new components refactor(home): replace endpoints with environments refactor(home): move endpoint-list component to home fix(home): show group name refactor(home): use switch for environment icon fix(kubeconfig): fix default case refactor(axios): use parse axios error refactor(home): use link components for navigate fix(home): align azure icon refactor(home): refactor stats refactor(home): export envstatusbadge refactor(home): remove unused bindings * chore(home): write tests for edge indicator * chore(home): basic stories for environment item * style(settings): reformat * fix(environments): add publicurl * refactor(home): use table components * refactor(datatables): merge useSearchBarState * refactor(home): fetch group in env item * chore(tests): basic tests * chore(home): test when no envs * refactor(tags): use axios for tagService * refactor(env-groups): use axios for getGroups * feat(app): ui-state context provider * refactor(home): create MotdPanel * refactor(app): create InformationPanel * feat(endpoints): fetch number of total endpoints * refactor(app): merge hooks * refactor(home): migrate view to react [EE-1810] fixes [EE-1810] refactor(home): wip use react view feat(home): show message if no endpoints refactor(home): show endpoint list refactor(home): don't use home to manage link refactor(home): move state refactor(home): check if edge using util refactor(home): move inf panels chore(home): tests refactor(home): load groups and tags in env-item refactor(settings): revert publicSettings change refactor(home): move confirm snapshot method * fix(home): show tags * fix(environments): handle missing snapshots * fix(kube/volumes): fetch pesistent volume claims * refactor(kube): remove use of endpointProvider * refactor(endpoints): set current endpoint * chore(home): add data-cy for tests * chore(tests): mock axios-progress-bar * refactor(home): move use env list to env module * feat(app): sync home view changes with ee * fix(home): sort page header * fix(app): fix tests * chore(github): use yarn cache * refactor(environments): load list of groups * chore(babel): remove auto 18n keys extraction * chore(environments): fix tests * refactor(k8s/application): use current endpoint * fix(app/header): add margin to header * refactor(app): remove unused types * refactor(app): use rq onError handler * refactor(home): wrap element with button
1 parent c442d93 commit 0f3c7b1

File tree

130 files changed

+2400
-1078
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+2400
-1078
lines changed

.github/workflows/lint.yml

+5-10
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,12 @@ jobs:
1818
runs-on: ubuntu-latest
1919

2020
steps:
21-
- name: Check out Git repository
22-
uses: actions/checkout@v2
23-
24-
- name: Set up Node.js
25-
uses: actions/setup-node@v1
21+
- uses: actions/checkout@v2
22+
- uses: actions/setup-node@v2
2623
with:
27-
node-version: 12
28-
29-
# ESLint and Prettier must be in `package.json`
30-
- name: Install Node.js dependencies
31-
run: yarn --frozen-lockfile
24+
node-version: '14'
25+
cache: 'yarn'
26+
- run: yarn --frozen-lockfile
3227

3328
- name: Run linters
3429
uses: wearerequired/lint-action@v1

.github/workflows/test-client.yaml

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
name: Test Frontend
22
on: push
33
jobs:
4-
build:
4+
test:
55
runs-on: ubuntu-latest
66
steps:
77
- uses: actions/checkout@v2
8-
- name: Install modules
9-
run: yarn --frozen-lockfile
8+
- uses: actions/setup-node@v2
9+
with:
10+
node-version: '14'
11+
cache: 'yarn'
12+
- run: yarn install --frozen-lockfile
13+
1014
- name: Run tests
1115
run: yarn test:client

api/http/handler/endpoints/endpoint_list.go

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
8080
}
8181

8282
filteredEndpoints := security.FilterEndpoints(endpoints, endpointGroups, securityContext)
83+
totalAvailableEndpoints := len(filteredEndpoints)
8384

8485
if endpointIDs != nil {
8586
filteredEndpoints = filteredEndpointsByIds(filteredEndpoints, endpointIDs)
@@ -127,6 +128,7 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
127128
}
128129

129130
w.Header().Set("X-Total-Count", strconv.Itoa(filteredEndpointCount))
131+
w.Header().Set("X-Total-Available", strconv.Itoa(totalAvailableEndpoints))
130132
return response.JSON(w, paginatedEndpoints)
131133
}
132134

app/__mocks__/axios-progress-bar.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export function loadProgressBar() {}

app/app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function onStartupAngular($rootScope, $state, $interval, LocalStorage, En
3838

3939
function ping(EndpointProvider, SystemService) {
4040
const endpoint = EndpointProvider.currentEndpoint();
41-
if (endpoint !== undefined && endpoint.Type == PortainerEndpointTypes.EdgeAgentOnDockerEnvironment) {
41+
if (endpoint && endpoint.Type == PortainerEndpointTypes.EdgeAgentOnDockerEnvironment) {
4242
SystemService.ping(endpoint.Id);
4343
}
4444
}

app/assets/css/app.css

+12-4
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,6 @@ a[ng-click] {
375375
background-color: var(--white-color) fff;
376376
}
377377

378-
.pagination-controls {
379-
margin-left: 10px;
380-
}
381-
382378
.user-box {
383379
margin-right: 25px;
384380
}
@@ -832,6 +828,18 @@ json-tree .branch-preview {
832828
align-items: center;
833829
}
834830

831+
.space-x-2 > * + * {
832+
margin-left: 0.5rem;
833+
}
834+
835+
.space-x-3 > * + * {
836+
margin-left: 0.75rem;
837+
}
838+
839+
.space-x-4 > * + * {
840+
margin-left: 1rem;
841+
}
842+
835843
.space-y-8 > * + * {
836844
margin-top: 2rem;
837845
}

app/assets/css/vendor-override.css

-27
Original file line numberDiff line numberDiff line change
@@ -222,33 +222,6 @@ json-tree .branch-preview {
222222
background-color: var(--bg-progress-color);
223223
}
224224

225-
.pagination > .disabled > span,
226-
.pagination > .disabled > span:hover,
227-
.pagination > .disabled > span:focus,
228-
.pagination > .disabled > a,
229-
.pagination > .disabled > a:hover,
230-
.pagination > .disabled > a:focus {
231-
color: var(--text-pagination-color);
232-
background-color: var(--bg-pagination-color);
233-
border-color: var(--border-pagination-color);
234-
}
235-
236-
.pagination > li > a,
237-
.pagination > li > span {
238-
background-color: var(--bg-pagination-span-color);
239-
border-color: var(--border-pagination-span-color);
240-
color: var(--text-pagination-span-color);
241-
}
242-
243-
.pagination > li > a:hover,
244-
.pagination > li > span:hover,
245-
.pagination > li > a:focus,
246-
.pagination > li > span:focus {
247-
background-color: var(--bg-pagination-hover-color);
248-
border-color: var(--border-pagination-hover-color);
249-
color: var(--text-pagination-span-hover-color);
250-
}
251-
252225
.ui-select-bootstrap .ui-select-choices-row > span {
253226
color: var(--text-ui-select-color);
254227
}

app/docker/containers/components/ContainersDatatable/ContainersDatatable.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import { ColumnVisibilityMenu } from '@/portainer/components/datatables/componen
3030
import { useRepeater } from '@/portainer/components/datatables/components/useRepeater';
3131
import { useDebounce } from '@/portainer/hooks/useDebounce';
3232
import {
33-
useSearchBarContext,
3433
SearchBar,
34+
useSearchBarState,
3535
} from '@/portainer/components/datatables/components/SearchBar';
3636
import type {
3737
ContainersTableSettings,
@@ -63,7 +63,7 @@ export function ContainersDatatable({
6363
}: ContainerTableProps) {
6464
const { settings, setTableSettings } =
6565
useTableSettings<ContainersTableSettings>();
66-
const [searchBarValue, setSearchBarValue] = useSearchBarContext();
66+
const [searchBarValue, setSearchBarValue] = useSearchBarState('containers');
6767

6868
const columns = useColumns();
6969

app/docker/containers/components/ContainersDatatable/ContainersDatatableContainer.tsx

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { react2angular } from '@/react-tools/react2angular';
22
import { EnvironmentProvider } from '@/portainer/environments/useEnvironment';
33
import { TableSettingsProvider } from '@/portainer/components/datatables/components/useTableSettings';
4-
import { SearchBarProvider } from '@/portainer/components/datatables/components/SearchBar';
54
import type { Environment } from '@/portainer/environments/types';
65

76
import {
@@ -30,10 +29,8 @@ export function ContainersDatatableContainer({
3029
return (
3130
<EnvironmentProvider environment={endpoint}>
3231
<TableSettingsProvider defaults={defaultSettings} storageKey={tableKey}>
33-
<SearchBarProvider storageKey={tableKey}>
34-
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
35-
<ContainersDatatable {...props} />
36-
</SearchBarProvider>
32+
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
33+
<ContainersDatatable {...props} />
3734
</TableSettingsProvider>
3835
</EnvironmentProvider>
3936
);

app/edge/devices/components/EdgeDevicesDatatable/EdgeDevicesDatatable.tsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
usePagination,
99
} from 'react-table';
1010
import { useRowSelectColumn } from '@lineup-lite/hooks';
11+
import _ from 'lodash';
1112

1213
import { Environment } from '@/portainer/environments/types';
1314
import { PaginationControls } from '@/portainer/components/pagination-controls';
@@ -27,7 +28,7 @@ import { ColumnVisibilityMenu } from '@/portainer/components/datatables/componen
2728
import { useRepeater } from '@/portainer/components/datatables/components/useRepeater';
2829
import { useDebounce } from '@/portainer/hooks/useDebounce';
2930
import {
30-
useSearchBarContext,
31+
useSearchBarState,
3132
SearchBar,
3233
} from '@/portainer/components/datatables/components/SearchBar';
3334
import { useRowSelect } from '@/portainer/components/datatables/components/useRowSelect';
@@ -38,34 +39,39 @@ import { EdgeDevicesDatatableSettings } from '@/edge/devices/components/EdgeDevi
3839
import { EdgeDevicesDatatableActions } from '@/edge/devices/components/EdgeDevicesDatatable/EdgeDevicesDatatableActions';
3940
import { AMTDevicesDatatable } from '@/edge/devices/components/AMTDevicesDatatable/AMTDevicesDatatable';
4041
import { TextTip } from '@/portainer/components/Tip/TextTip';
42+
import { EnvironmentGroup } from '@/portainer/environment-groups/types';
4143

4244
import { RowProvider } from './columns/RowContext';
4345
import { useColumns } from './columns';
4446
import styles from './EdgeDevicesDatatable.module.css';
4547

4648
export interface EdgeDevicesTableProps {
49+
storageKey: string;
4750
isEnabled: boolean;
4851
isFdoEnabled: boolean;
4952
isOpenAmtEnabled: boolean;
5053
disableTrustOnFirstConnect: boolean;
5154
mpsServer: string;
5255
dataset: Environment[];
56+
groups: EnvironmentGroup[];
5357
onRefresh(): Promise<void>;
5458
setLoadingMessage(message: string): void;
5559
}
5660

5761
export function EdgeDevicesDatatable({
62+
storageKey,
5863
isFdoEnabled,
5964
isOpenAmtEnabled,
6065
disableTrustOnFirstConnect,
6166
mpsServer,
6267
dataset,
68+
groups,
6369
onRefresh,
6470
setLoadingMessage,
6571
}: EdgeDevicesTableProps) {
6672
const { settings, setTableSettings } =
6773
useTableSettings<EdgeDeviceTableSettings>();
68-
const [searchBarValue, setSearchBarValue] = useSearchBarContext();
74+
const [searchBarValue, setSearchBarValue] = useSearchBarState(storageKey);
6975

7076
const columns = useColumns();
7177

@@ -131,6 +137,8 @@ export function EdgeDevicesDatatable({
131137
environment.AMTDeviceGUID && environment.AMTDeviceGUID !== ''
132138
);
133139

140+
const groupsById = _.groupBy(groups, 'Id');
141+
134142
return (
135143
<TableContainer>
136144
<TableTitle icon="fa-plug" label="Edge Devices">
@@ -201,12 +209,13 @@ export function EdgeDevicesDatatable({
201209
{page.map((row) => {
202210
prepareRow(row);
203211
const { key, className, role, style } = row.getRowProps();
204-
212+
const group = groupsById[row.original.GroupId];
205213
return (
206214
<RowProvider
207215
key={key}
208216
disableTrustOnFirstConnect={disableTrustOnFirstConnect}
209217
isOpenAmtEnabled={isOpenAmtEnabled}
218+
groupName={group[0]?.Name}
210219
>
211220
<TableRow<Environment>
212221
cells={row.cells}

app/edge/devices/components/EdgeDevicesDatatable/EdgeDevicesDatatableContainer.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { react2angular } from '@/react-tools/react2angular';
22
import { TableSettingsProvider } from '@/portainer/components/datatables/components/useTableSettings';
3-
import { SearchBarProvider } from '@/portainer/components/datatables/components/SearchBar';
43

54
import {
65
EdgeDevicesDatatable,
@@ -18,19 +17,20 @@ export function EdgeDevicesDatatableContainer({
1817
sortBy: { id: 'state', desc: false },
1918
};
2019

20+
const storageKey = 'edgeDevices';
21+
2122
return (
22-
<TableSettingsProvider defaults={defaultSettings} storageKey="edgeDevices">
23-
<SearchBarProvider storageKey="edgeDevices">
24-
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
25-
<EdgeDevicesDatatable {...props} />
26-
</SearchBarProvider>
23+
<TableSettingsProvider defaults={defaultSettings} storageKey={storageKey}>
24+
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
25+
<EdgeDevicesDatatable {...props} storageKey={storageKey} />
2726
</TableSettingsProvider>
2827
);
2928
}
3029

3130
export const EdgeDevicesDatatableAngular = react2angular(
3231
EdgeDevicesDatatableContainer,
3332
[
33+
'groups',
3434
'dataset',
3535
'onRefresh',
3636
'setLoadingMessage',

app/edge/devices/components/EdgeDevicesDatatable/columns/RowContext.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,26 @@ import { createContext, useContext, useMemo, PropsWithChildren } from 'react';
33
interface RowContextState {
44
disableTrustOnFirstConnect: boolean;
55
isOpenAmtEnabled: boolean;
6+
groupName?: string;
67
}
78

89
const RowContext = createContext<RowContextState | null>(null);
910

1011
export interface RowProviderProps {
1112
disableTrustOnFirstConnect: boolean;
13+
groupName?: string;
1214
isOpenAmtEnabled: boolean;
1315
}
1416

1517
export function RowProvider({
1618
disableTrustOnFirstConnect,
19+
groupName,
1720
isOpenAmtEnabled,
1821
children,
1922
}: PropsWithChildren<RowProviderProps>) {
2023
const state = useMemo(
21-
() => ({ disableTrustOnFirstConnect, isOpenAmtEnabled }),
22-
[disableTrustOnFirstConnect, isOpenAmtEnabled]
24+
() => ({ disableTrustOnFirstConnect, groupName, isOpenAmtEnabled }),
25+
[disableTrustOnFirstConnect, groupName, isOpenAmtEnabled]
2326
);
2427

2528
return <RowContext.Provider value={state}>{children}</RowContext.Provider>;

app/edge/devices/components/EdgeDevicesDatatable/columns/group.tsx

+10-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@ import { Column } from 'react-table';
33
import { Environment } from '@/portainer/environments/types';
44
import { DefaultFilter } from '@/portainer/components/datatables/components/Filter';
55

6+
import { useRowContext } from './RowContext';
7+
68
export const group: Column<Environment> = {
79
Header: 'Group',
8-
accessor: (row) => row.GroupName || '-',
10+
accessor: (row) => row.GroupId,
11+
Cell: GroupCell,
912
id: 'groupName',
1013
Filter: DefaultFilter,
1114
canHide: true,
1215
};
16+
17+
function GroupCell() {
18+
const { groupName } = useRowContext();
19+
20+
return groupName;
21+
}

app/edge/views/edge-devices/edgeDevicesView/edgeDevicesView.html

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<div class="col-sm-12">
2929
<edge-devices-datatable
3030
dataset="($ctrl.edgeDevices)"
31+
groups="($ctrl.groups)"
3132
is-fdo-enabled="($ctrl.isFDOEnabled)"
3233
is-open-amt-enabled="($ctrl.isOpenAMTEnabled)"
3334
disable-trust-on-first-connect="($ctrl.disableTrustOnFirstConnect)"

app/edge/views/edge-devices/edgeDevicesView/edgeDevicesViewController.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import EndpointHelper from 'Portainer/helpers/endpointHelper';
21
import { getEndpoints } from 'Portainer/environments/environment.service';
32
import { EnvironmentType } from 'Portainer/environments/types';
43

@@ -13,7 +12,7 @@ export function EdgeDevicesViewController($q, $async, EndpointService, GroupServ
1312
return $async(async () => {
1413
try {
1514
const [endpointsResponse, groups] = await Promise.all([getEndpoints(0, 100, { types: [EnvironmentType.EdgeAgentOnDocker] }), GroupService.groups()]);
16-
EndpointHelper.mapGroupNameToEndpoint(endpointsResponse.value, groups);
15+
ctrl.groups = groups;
1716
ctrl.edgeDevices = endpointsResponse.value;
1817
} catch (err) {
1918
Notifications.error('Failure', err, 'Unable to retrieve edge devices');

app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatable.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ angular.module('portainer.kubernetes').component('kubernetesResourcePoolsDatatab
22
templateUrl: './resourcePoolsDatatable.html',
33
controller: 'KubernetesResourcePoolsDatatableController',
44
bindings: {
5+
endpoint: '<',
56
titleText: '@',
67
titleIcon: '@',
78
dataset: '<',
@@ -10,6 +11,5 @@ angular.module('portainer.kubernetes').component('kubernetesResourcePoolsDatatab
1011
reverseOrder: '<',
1112
removeAction: '<',
1213
refreshCallback: '<',
13-
endpoint: '<',
1414
},
1515
});

app/kubernetes/components/helm/helm-templates/helm-add-repository/helm-add-repository.controller.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
export default class HelmAddRepositoryController {
22
/* @ngInject */
3-
constructor($state, $async, HelmService, Notifications, EndpointProvider) {
3+
constructor($state, $async, HelmService, Notifications) {
44
this.$state = $state;
55
this.$async = $async;
66
this.HelmService = HelmService;
77
this.Notifications = Notifications;
8-
this.EndpointProvider = EndpointProvider;
98
}
109

1110
doesRepoExist() {
@@ -19,7 +18,7 @@ export default class HelmAddRepositoryController {
1918
async addRepository() {
2019
this.state.isAddingRepo = true;
2120
try {
22-
await this.HelmService.addHelmRepository(this.EndpointProvider.currentEndpoint().Id, { url: this.state.repository });
21+
await this.HelmService.addHelmRepository(this.endpoint.Id, { url: this.state.repository });
2322
this.Notifications.success('Helm repository added successfully');
2423
this.$state.reload(this.$state.current);
2524
} catch (err) {

0 commit comments

Comments
 (0)