mirror of
https://github.com/portainer/portainer.git
synced 2025-08-06 06:15:22 +02:00
feat(edge): show correct heartbeat and sync aeec changes [EE-2876] (#6769)
This commit is contained in:
parent
76d1b70644
commit
e217ac7121
44 changed files with 1099 additions and 307 deletions
214
app/edge/EdgeDevices/WaitingRoomView/Datatable/Datatable.tsx
Normal file
214
app/edge/EdgeDevices/WaitingRoomView/Datatable/Datatable.tsx
Normal file
|
@ -0,0 +1,214 @@
|
|||
import {
|
||||
Column,
|
||||
useGlobalFilter,
|
||||
usePagination,
|
||||
useRowSelect,
|
||||
useSortBy,
|
||||
useTable,
|
||||
} from 'react-table';
|
||||
import { useRowSelectColumn } from '@lineup-lite/hooks';
|
||||
|
||||
import { Button } from '@/portainer/components/Button';
|
||||
import { Table } from '@/portainer/components/datatables/components';
|
||||
import {
|
||||
SearchBar,
|
||||
useSearchBarState,
|
||||
} from '@/portainer/components/datatables/components/SearchBar';
|
||||
import { SelectedRowsCount } from '@/portainer/components/datatables/components/SelectedRowsCount';
|
||||
import { PaginationControls } from '@/portainer/components/pagination-controls';
|
||||
import { Environment } from '@/portainer/environments/types';
|
||||
import { useTableSettings } from '@/portainer/components/datatables/components/useTableSettings';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
|
||||
import { useAssociateDeviceMutation } from '../queries';
|
||||
|
||||
import { TableSettings } from './types';
|
||||
|
||||
const columns: readonly Column<Environment>[] = [
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: (row) => row.Name,
|
||||
id: 'name',
|
||||
disableFilters: true,
|
||||
Filter: () => null,
|
||||
canHide: false,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
Header: 'Edge ID',
|
||||
accessor: (row) => row.EdgeID,
|
||||
id: 'edge-id',
|
||||
disableFilters: true,
|
||||
Filter: () => null,
|
||||
canHide: false,
|
||||
sortType: 'string',
|
||||
},
|
||||
] as const;
|
||||
|
||||
interface Props {
|
||||
devices: Environment[];
|
||||
isLoading: boolean;
|
||||
totalCount: number;
|
||||
storageKey: string;
|
||||
}
|
||||
|
||||
export function DataTable({
|
||||
devices,
|
||||
storageKey,
|
||||
isLoading,
|
||||
totalCount,
|
||||
}: Props) {
|
||||
const associateMutation = useAssociateDeviceMutation();
|
||||
|
||||
const [searchBarValue, setSearchBarValue] = useSearchBarState(storageKey);
|
||||
const { settings, setTableSettings } = useTableSettings<TableSettings>();
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
page,
|
||||
prepareRow,
|
||||
selectedFlatRows,
|
||||
|
||||
gotoPage,
|
||||
setPageSize,
|
||||
|
||||
setGlobalFilter,
|
||||
state: { pageIndex, pageSize },
|
||||
} = useTable<Environment>(
|
||||
{
|
||||
defaultCanFilter: false,
|
||||
columns,
|
||||
data: devices,
|
||||
|
||||
initialState: {
|
||||
pageSize: settings.pageSize || 10,
|
||||
sortBy: [settings.sortBy],
|
||||
globalFilter: searchBarValue,
|
||||
},
|
||||
isRowSelectable() {
|
||||
return true;
|
||||
},
|
||||
autoResetSelectedRows: false,
|
||||
getRowId(originalRow: Environment) {
|
||||
return originalRow.Id.toString();
|
||||
},
|
||||
selectColumnWidth: 5,
|
||||
},
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
|
||||
usePagination,
|
||||
useRowSelect,
|
||||
useRowSelectColumn
|
||||
);
|
||||
|
||||
const tableProps = getTableProps();
|
||||
const tbodyProps = getTableBodyProps();
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<Table.Container>
|
||||
<Table.Title label="Edge Devices Waiting Room" icon="" />
|
||||
<Table.Actions>
|
||||
<Button
|
||||
onClick={() =>
|
||||
handleAssociateDevice(selectedFlatRows.map((r) => r.original))
|
||||
}
|
||||
disabled={selectedFlatRows.length === 0}
|
||||
>
|
||||
Associate Device
|
||||
</Button>
|
||||
</Table.Actions>
|
||||
|
||||
<SearchBar onChange={handleSearchBarChange} value={searchBarValue} />
|
||||
|
||||
<Table
|
||||
className={tableProps.className}
|
||||
role={tableProps.role}
|
||||
style={tableProps.style}
|
||||
>
|
||||
<thead>
|
||||
{headerGroups.map((headerGroup) => {
|
||||
const { key, className, role, style } =
|
||||
headerGroup.getHeaderGroupProps();
|
||||
|
||||
return (
|
||||
<Table.HeaderRow<Environment>
|
||||
key={key}
|
||||
className={className}
|
||||
role={role}
|
||||
style={style}
|
||||
headers={headerGroup.headers}
|
||||
onSortChange={handleSortChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</thead>
|
||||
|
||||
<tbody
|
||||
className={tbodyProps.className}
|
||||
role={tbodyProps.role}
|
||||
style={tbodyProps.style}
|
||||
>
|
||||
<Table.Content
|
||||
emptyContent="No Edge Devices found"
|
||||
prepareRow={prepareRow}
|
||||
rows={page}
|
||||
isLoading={isLoading}
|
||||
renderRow={(row, { key, className, role, style }) => (
|
||||
<Table.Row
|
||||
cells={row.cells}
|
||||
key={key}
|
||||
className={className}
|
||||
role={role}
|
||||
style={style}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<Table.Footer>
|
||||
<SelectedRowsCount value={selectedFlatRows.length} />
|
||||
<PaginationControls
|
||||
showAll
|
||||
pageLimit={pageSize}
|
||||
page={pageIndex + 1}
|
||||
onPageChange={(p) => gotoPage(p - 1)}
|
||||
totalCount={totalCount}
|
||||
onPageLimitChange={handlePageLimitChange}
|
||||
/>
|
||||
</Table.Footer>
|
||||
</Table.Container>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
function handleSortChange(colId: string, desc: boolean) {
|
||||
setTableSettings({ sortBy: { id: colId, desc } });
|
||||
}
|
||||
|
||||
function handlePageLimitChange(pageSize: number) {
|
||||
setPageSize(pageSize);
|
||||
setTableSettings({ pageSize });
|
||||
}
|
||||
|
||||
function handleSearchBarChange(value: string) {
|
||||
setGlobalFilter(value);
|
||||
setSearchBarValue(value);
|
||||
}
|
||||
|
||||
function handleAssociateDevice(devices: Environment[]) {
|
||||
associateMutation.mutate(
|
||||
devices.map((d) => d.Id),
|
||||
{
|
||||
onSuccess() {
|
||||
notifySuccess('Edge devices associated successfully');
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
8
app/edge/EdgeDevices/WaitingRoomView/Datatable/types.ts
Normal file
8
app/edge/EdgeDevices/WaitingRoomView/Datatable/types.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import {
|
||||
PaginationTableSettings,
|
||||
SortableTableSettings,
|
||||
} from '@/portainer/components/datatables/types';
|
||||
|
||||
export interface TableSettings
|
||||
extends SortableTableSettings,
|
||||
PaginationTableSettings {}
|
47
app/edge/EdgeDevices/WaitingRoomView/WaitingRoomView.tsx
Normal file
47
app/edge/EdgeDevices/WaitingRoomView/WaitingRoomView.tsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { useRouter } from '@uirouter/react';
|
||||
|
||||
import { TableSettingsProvider } from '@/portainer/components/datatables/components/useTableSettings';
|
||||
import { PageHeader } from '@/portainer/components/PageHeader';
|
||||
import { useEnvironmentList } from '@/portainer/environments/queries';
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
|
||||
import { DataTable } from './Datatable/Datatable';
|
||||
import { TableSettings } from './Datatable/types';
|
||||
|
||||
export function WaitingRoomView() {
|
||||
const storageKey = 'edge-devices-waiting-room';
|
||||
const router = useRouter();
|
||||
const { environments, isLoading, totalCount } = useEnvironmentList({
|
||||
edgeDeviceFilter: 'untrusted',
|
||||
});
|
||||
|
||||
if (process.env.PORTAINER_EDITION !== 'BE') {
|
||||
router.stateService.go('edge.devices');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Waiting Room"
|
||||
breadcrumbs={[
|
||||
{ label: 'Edge Devices', link: 'edge.devices' },
|
||||
{ label: 'Waiting Room' },
|
||||
]}
|
||||
/>
|
||||
<TableSettingsProvider<TableSettings>
|
||||
defaults={{ pageSize: 10, sortBy: { desc: false, id: 'name' } }}
|
||||
storageKey={storageKey}
|
||||
>
|
||||
<DataTable
|
||||
devices={environments}
|
||||
totalCount={totalCount}
|
||||
isLoading={isLoading}
|
||||
storageKey={storageKey}
|
||||
/>
|
||||
</TableSettingsProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const WaitingRoomViewAngular = r2a(WaitingRoomView, []);
|
1
app/edge/EdgeDevices/WaitingRoomView/index.ts
Normal file
1
app/edge/EdgeDevices/WaitingRoomView/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { WaitingRoomView, WaitingRoomViewAngular } from './WaitingRoomView';
|
33
app/edge/EdgeDevices/WaitingRoomView/queries.ts
Normal file
33
app/edge/EdgeDevices/WaitingRoomView/queries.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { useMutation, useQueryClient } from 'react-query';
|
||||
|
||||
import { EnvironmentId } from '@/portainer/environments/types';
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { promiseSequence } from '@/portainer/helpers/promise-utils';
|
||||
|
||||
export function useAssociateDeviceMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
(ids: EnvironmentId[]) =>
|
||||
promiseSequence(ids.map((id) => () => associateDevice(id))),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(['environments']);
|
||||
},
|
||||
meta: {
|
||||
error: {
|
||||
title: 'Failure',
|
||||
message: 'Failed to associate devices',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function associateDevice(environmentId: EnvironmentId) {
|
||||
try {
|
||||
await axios.post(`/endpoints/${environmentId}/edge/trust`);
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error, 'Failed to associate device');
|
||||
}
|
||||
}
|
|
@ -3,134 +3,150 @@ import angular from 'angular';
|
|||
import edgeStackModule from './views/edge-stacks';
|
||||
import edgeDevicesModule from './devices';
|
||||
import { componentsModule } from './components';
|
||||
import { WaitingRoomViewAngular } from './EdgeDevices/WaitingRoomView';
|
||||
|
||||
angular.module('portainer.edge', [edgeStackModule, edgeDevicesModule, componentsModule]).config(function config($stateRegistryProvider) {
|
||||
const edge = {
|
||||
name: 'edge',
|
||||
url: '/edge',
|
||||
parent: 'root',
|
||||
abstract: true,
|
||||
};
|
||||
angular
|
||||
.module('portainer.edge', [edgeStackModule, edgeDevicesModule, componentsModule])
|
||||
.component('waitingRoomView', WaitingRoomViewAngular)
|
||||
.config(function config($stateRegistryProvider) {
|
||||
const edge = {
|
||||
name: 'edge',
|
||||
url: '/edge',
|
||||
parent: 'root',
|
||||
abstract: true,
|
||||
};
|
||||
|
||||
const groups = {
|
||||
name: 'edge.groups',
|
||||
url: '/groups',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeGroupsView',
|
||||
const groups = {
|
||||
name: 'edge.groups',
|
||||
url: '/groups',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeGroupsView',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const groupsNew = {
|
||||
name: 'edge.groups.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createEdgeGroupView',
|
||||
const groupsNew = {
|
||||
name: 'edge.groups.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createEdgeGroupView',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const groupsEdit = {
|
||||
name: 'edge.groups.edit',
|
||||
url: '/:groupId',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'editEdgeGroupView',
|
||||
const groupsEdit = {
|
||||
name: 'edge.groups.edit',
|
||||
url: '/:groupId',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'editEdgeGroupView',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const stacks = {
|
||||
name: 'edge.stacks',
|
||||
url: '/stacks',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeStacksView',
|
||||
const stacks = {
|
||||
name: 'edge.stacks',
|
||||
url: '/stacks',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeStacksView',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const stacksNew = {
|
||||
name: 'edge.stacks.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createEdgeStackView',
|
||||
const stacksNew = {
|
||||
name: 'edge.stacks.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createEdgeStackView',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const stacksEdit = {
|
||||
name: 'edge.stacks.edit',
|
||||
url: '/:stackId',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'editEdgeStackView',
|
||||
const stacksEdit = {
|
||||
name: 'edge.stacks.edit',
|
||||
url: '/:stackId',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'editEdgeStackView',
|
||||
},
|
||||
},
|
||||
},
|
||||
params: {
|
||||
tab: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const edgeJobs = {
|
||||
name: 'edge.jobs',
|
||||
url: '/jobs',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeJobsView',
|
||||
params: {
|
||||
tab: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const edgeJob = {
|
||||
name: 'edge.jobs.job',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeJobView',
|
||||
const edgeJobs = {
|
||||
name: 'edge.jobs',
|
||||
url: '/jobs',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeJobsView',
|
||||
},
|
||||
},
|
||||
},
|
||||
params: {
|
||||
tab: 0,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const edgeJobCreation = {
|
||||
name: 'edge.jobs.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createEdgeJobView',
|
||||
const edgeJob = {
|
||||
name: 'edge.jobs.job',
|
||||
url: '/:id',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeJobView',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const edgeDevices = {
|
||||
name: 'edge.devices',
|
||||
url: '/devices',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeDevicesView',
|
||||
params: {
|
||||
tab: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(edge);
|
||||
const edgeJobCreation = {
|
||||
name: 'edge.jobs.new',
|
||||
url: '/new',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'createEdgeJobView',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(groups);
|
||||
$stateRegistryProvider.register(groupsNew);
|
||||
$stateRegistryProvider.register(groupsEdit);
|
||||
const edgeDevices = {
|
||||
name: 'edge.devices',
|
||||
url: '/devices',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'edgeDevicesView',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$stateRegistryProvider.register(stacks);
|
||||
$stateRegistryProvider.register(stacksNew);
|
||||
$stateRegistryProvider.register(stacksEdit);
|
||||
if (process.env.PORTAINER_EDITION === 'BE') {
|
||||
$stateRegistryProvider.register({
|
||||
name: 'edge.devices.waiting-room',
|
||||
url: '/waiting-room',
|
||||
views: {
|
||||
'content@': {
|
||||
component: 'waitingRoomView',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
$stateRegistryProvider.register(edgeJobs);
|
||||
$stateRegistryProvider.register(edgeJob);
|
||||
$stateRegistryProvider.register(edgeJobCreation);
|
||||
$stateRegistryProvider.register(edge);
|
||||
|
||||
$stateRegistryProvider.register(edgeDevices);
|
||||
});
|
||||
$stateRegistryProvider.register(groups);
|
||||
$stateRegistryProvider.register(groupsNew);
|
||||
$stateRegistryProvider.register(groupsEdit);
|
||||
|
||||
$stateRegistryProvider.register(stacks);
|
||||
$stateRegistryProvider.register(stacksNew);
|
||||
$stateRegistryProvider.register(stacksEdit);
|
||||
|
||||
$stateRegistryProvider.register(edgeJobs);
|
||||
$stateRegistryProvider.register(edgeJob);
|
||||
$stateRegistryProvider.register(edgeJobCreation);
|
||||
|
||||
$stateRegistryProvider.register(edgeDevices);
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ export function EdgePropertiesForm({
|
|||
}: Props) {
|
||||
return (
|
||||
<form className="form-horizontal">
|
||||
<FormSectionTitle>Edge script settings</FormSectionTitle>
|
||||
<FormSectionTitle>Edge agent deployment script</FormSectionTitle>
|
||||
|
||||
<OsSelector
|
||||
value={values.os}
|
||||
|
|
|
@ -50,7 +50,7 @@ export interface EdgeDevicesTableProps {
|
|||
isEnabled: boolean;
|
||||
isFdoEnabled: boolean;
|
||||
isOpenAmtEnabled: boolean;
|
||||
disableTrustOnFirstConnect: boolean;
|
||||
showWaitingRoomLink: boolean;
|
||||
mpsServer: string;
|
||||
dataset: Environment[];
|
||||
groups: EnvironmentGroup[];
|
||||
|
@ -62,7 +62,7 @@ export function EdgeDevicesDatatable({
|
|||
storageKey,
|
||||
isFdoEnabled,
|
||||
isOpenAmtEnabled,
|
||||
disableTrustOnFirstConnect,
|
||||
showWaitingRoomLink,
|
||||
mpsServer,
|
||||
dataset,
|
||||
groups,
|
||||
|
@ -164,6 +164,7 @@ export function EdgeDevicesDatatable({
|
|||
isFDOEnabled={isFdoEnabled}
|
||||
isOpenAMTEnabled={isOpenAmtEnabled}
|
||||
setLoadingMessage={setLoadingMessage}
|
||||
showWaitingRoomLink={showWaitingRoomLink}
|
||||
/>
|
||||
</TableActions>
|
||||
|
||||
|
@ -216,7 +217,6 @@ export function EdgeDevicesDatatable({
|
|||
return (
|
||||
<RowProvider
|
||||
key={key}
|
||||
disableTrustOnFirstConnect={disableTrustOnFirstConnect}
|
||||
isOpenAmtEnabled={isOpenAmtEnabled}
|
||||
groupName={group[0]?.Name}
|
||||
>
|
||||
|
|
|
@ -7,12 +7,14 @@ import { promptAsync } from '@/portainer/services/modal.service/prompt';
|
|||
import * as notifications from '@/portainer/services/notifications';
|
||||
import { activateDevice } from '@/portainer/hostmanagement/open-amt/open-amt.service';
|
||||
import { deleteEndpoint } from '@/portainer/environments/environment.service';
|
||||
import { Link } from '@/portainer/components/Link';
|
||||
|
||||
interface Props {
|
||||
selectedItems: Environment[];
|
||||
isFDOEnabled: boolean;
|
||||
isOpenAMTEnabled: boolean;
|
||||
setLoadingMessage(message: string): void;
|
||||
showWaitingRoomLink: boolean;
|
||||
}
|
||||
|
||||
export function EdgeDevicesDatatableActions({
|
||||
|
@ -20,6 +22,7 @@ export function EdgeDevicesDatatableActions({
|
|||
isOpenAMTEnabled,
|
||||
isFDOEnabled,
|
||||
setLoadingMessage,
|
||||
showWaitingRoomLink,
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -48,6 +51,12 @@ export function EdgeDevicesDatatableActions({
|
|||
Associate with OpenAMT
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{showWaitingRoomLink && (
|
||||
<Link to="edge.devices.waiting-room">
|
||||
<Button>Waiting Room</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export const EdgeDevicesDatatableAngular = react2angular(
|
|||
'onRefresh',
|
||||
'setLoadingMessage',
|
||||
'isFdoEnabled',
|
||||
'disableTrustOnFirstConnect',
|
||||
'showWaitingRoomLink',
|
||||
'isOpenAmtEnabled',
|
||||
'mpsServer',
|
||||
]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { createContext, useContext, useMemo, PropsWithChildren } from 'react';
|
||||
|
||||
interface RowContextState {
|
||||
disableTrustOnFirstConnect: boolean;
|
||||
isOpenAmtEnabled: boolean;
|
||||
groupName?: string;
|
||||
}
|
||||
|
@ -9,20 +8,18 @@ interface RowContextState {
|
|||
const RowContext = createContext<RowContextState | null>(null);
|
||||
|
||||
export interface RowProviderProps {
|
||||
disableTrustOnFirstConnect: boolean;
|
||||
groupName?: string;
|
||||
isOpenAmtEnabled: boolean;
|
||||
}
|
||||
|
||||
export function RowProvider({
|
||||
disableTrustOnFirstConnect,
|
||||
groupName,
|
||||
isOpenAmtEnabled,
|
||||
children,
|
||||
}: PropsWithChildren<RowProviderProps>) {
|
||||
const state = useMemo(
|
||||
() => ({ disableTrustOnFirstConnect, groupName, isOpenAmtEnabled }),
|
||||
[disableTrustOnFirstConnect, groupName, isOpenAmtEnabled]
|
||||
() => ({ groupName, isOpenAmtEnabled }),
|
||||
[groupName, isOpenAmtEnabled]
|
||||
);
|
||||
|
||||
return <RowContext.Provider value={state}>{children}</RowContext.Provider>;
|
||||
|
|
|
@ -4,14 +4,9 @@ import { useRouter, useSref } from '@uirouter/react';
|
|||
|
||||
import { Environment } from '@/portainer/environments/types';
|
||||
import { ActionsMenu } from '@/portainer/components/datatables/components/ActionsMenu';
|
||||
import {
|
||||
snapshotEndpoint,
|
||||
trustEndpoint,
|
||||
} from '@/portainer/environments/environment.service';
|
||||
import { snapshotEndpoint } from '@/portainer/environments/environment.service';
|
||||
import * as notifications from '@/portainer/services/notifications';
|
||||
import { getRoute } from '@/portainer/environments/utils';
|
||||
import { confirmAsync } from '@/portainer/services/modal.service/confirm';
|
||||
import { useRowContext } from '@/edge/devices/components/EdgeDevicesDatatable/columns/RowContext';
|
||||
|
||||
export const actions: Column<Environment> = {
|
||||
Header: 'Actions',
|
||||
|
@ -39,8 +34,6 @@ export function ActionsCell({
|
|||
|
||||
const showRefreshSnapshot = false; // remove and show MenuItem when feature is available
|
||||
|
||||
const { disableTrustOnFirstConnect } = useRowContext();
|
||||
|
||||
return (
|
||||
<ActionsMenu>
|
||||
<MenuLink href={browseLinkProps.href} onClick={browseLinkProps.onClick}>
|
||||
|
@ -51,9 +44,6 @@ export function ActionsCell({
|
|||
Refresh Snapshot
|
||||
</MenuItem>
|
||||
)}
|
||||
{disableTrustOnFirstConnect && !environment.UserTrusted && (
|
||||
<MenuLink onClick={trustDevice}>Trust</MenuLink>
|
||||
)}
|
||||
</ActionsMenu>
|
||||
);
|
||||
|
||||
|
@ -71,37 +61,4 @@ export function ActionsCell({
|
|||
await router.stateService.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function trustDevice() {
|
||||
const confirmed = await confirmAsync({
|
||||
title: '',
|
||||
message: `Mark ${environment.Name} as trusted?`,
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-default',
|
||||
},
|
||||
confirm: {
|
||||
label: 'Trust',
|
||||
className: 'btn-primary',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await trustEndpoint(environment.Id);
|
||||
} catch (err) {
|
||||
notifications.error(
|
||||
'Failure',
|
||||
err as Error,
|
||||
'An error occurred while trusting the environment'
|
||||
);
|
||||
} finally {
|
||||
await router.stateService.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { CellProps, Column } from 'react-table';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { Environment, EnvironmentStatus } from '@/portainer/environments/types';
|
||||
import { useRowContext } from '@/edge/devices/components/EdgeDevicesDatatable/columns/RowContext';
|
||||
import { Environment } from '@/portainer/environments/types';
|
||||
import { EdgeIndicator } from '@/portainer/home/EnvironmentList/EnvironmentItem/EdgeIndicator';
|
||||
|
||||
export const heartbeat: Column<Environment> = {
|
||||
Header: 'Heartbeat',
|
||||
|
@ -16,35 +15,12 @@ export const heartbeat: Column<Environment> = {
|
|||
export function StatusCell({
|
||||
row: { original: environment },
|
||||
}: CellProps<Environment>) {
|
||||
const { disableTrustOnFirstConnect } = useRowContext();
|
||||
|
||||
if (disableTrustOnFirstConnect && !environment.UserTrusted) {
|
||||
return <span className="label label-default">untrusted</span>;
|
||||
}
|
||||
|
||||
if (!environment.LastCheckInDate) {
|
||||
return (
|
||||
<span className="label label-default">
|
||||
<s>associated</s>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<i
|
||||
className={clsx(
|
||||
'fa',
|
||||
'fa-heartbeat',
|
||||
environmentStatusLabel(environment.Status)
|
||||
)}
|
||||
aria-hidden="true"
|
||||
<EdgeIndicator
|
||||
checkInInterval={environment.EdgeCheckinInterval}
|
||||
edgeId={environment.EdgeID}
|
||||
lastCheckInDate={environment.LastCheckInDate}
|
||||
queryDate={environment.QueryDate}
|
||||
/>
|
||||
);
|
||||
|
||||
function environmentStatusLabel(status: EnvironmentStatus) {
|
||||
if (status === EnvironmentStatus.Up) {
|
||||
return 'green-icon';
|
||||
}
|
||||
return 'orange-icon';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
export interface EdgeDeviceTableSettings {
|
||||
hiddenColumns: string[];
|
||||
autoRefreshRate: number;
|
||||
pageSize: number;
|
||||
sortBy: { id: string; desc: boolean };
|
||||
}
|
||||
import {
|
||||
PaginationTableSettings,
|
||||
RefreshableTableSettings,
|
||||
SettableColumnsTableSettings,
|
||||
SortableTableSettings,
|
||||
} from '@/portainer/components/datatables/types';
|
||||
|
||||
export interface FDOProfilesTableSettings {
|
||||
pageSize: number;
|
||||
sortBy: { id: string; desc: boolean };
|
||||
}
|
||||
export interface EdgeDeviceTableSettings
|
||||
extends SortableTableSettings,
|
||||
PaginationTableSettings,
|
||||
SettableColumnsTableSettings,
|
||||
RefreshableTableSettings {}
|
||||
|
||||
export interface FDOProfilesTableSettings
|
||||
extends SortableTableSettings,
|
||||
PaginationTableSettings {}
|
||||
|
||||
export enum DeviceAction {
|
||||
PowerOn = 'power on',
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
groups="($ctrl.groups)"
|
||||
is-fdo-enabled="($ctrl.isFDOEnabled)"
|
||||
is-open-amt-enabled="($ctrl.isOpenAMTEnabled)"
|
||||
disable-trust-on-first-connect="($ctrl.disableTrustOnFirstConnect)"
|
||||
show-waiting-room-link="($ctrl.showWaitingRoomLink)"
|
||||
mps-server="($ctrl.mpsServer)"
|
||||
on-refresh="($ctrl.getEnvironments)"
|
||||
set-loading-message="($ctrl.setLoadingMessage)"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { getEndpoints } from 'Portainer/environments/environment.service';
|
||||
import { EnvironmentType } from 'Portainer/environments/types';
|
||||
|
||||
angular.module('portainer.edge').controller('EdgeDevicesViewController', EdgeDevicesViewController);
|
||||
/* @ngInject */
|
||||
|
@ -11,7 +10,7 @@ export function EdgeDevicesViewController($q, $async, EndpointService, GroupServ
|
|||
this.getEnvironments = function () {
|
||||
return $async(async () => {
|
||||
try {
|
||||
const [endpointsResponse, groups] = await Promise.all([getEndpoints(0, 100, { types: [EnvironmentType.EdgeAgentOnDocker] }), GroupService.groups()]);
|
||||
const [endpointsResponse, groups] = await Promise.all([getEndpoints(0, 100, { edgeDeviceFilter: 'trusted' }), GroupService.groups()]);
|
||||
ctrl.groups = groups;
|
||||
ctrl.edgeDevices = endpointsResponse.value;
|
||||
} catch (err) {
|
||||
|
@ -27,7 +26,7 @@ export function EdgeDevicesViewController($q, $async, EndpointService, GroupServ
|
|||
const settings = await SettingsService.settings();
|
||||
|
||||
ctrl.isFDOEnabled = settings && settings.EnableEdgeComputeFeatures && settings.fdoConfiguration && settings.fdoConfiguration.enabled;
|
||||
ctrl.disableTrustOnFirstConnect = settings && settings.EnableEdgeComputeFeatures && settings.DisableTrustOnFirstConnect;
|
||||
ctrl.showWaitingRoomLink = process.env.PORTAINER_EDITION === 'BE' && settings && settings.EnableEdgeComputeFeatures && !settings.TrustOnFirstConnect;
|
||||
ctrl.isOpenAMTEnabled = settings && settings.EnableEdgeComputeFeatures && settings.openAMTConfiguration && settings.openAMTConfiguration.enabled;
|
||||
ctrl.mpsServer = ctrl.isOpenAMTEnabled ? settings.openAMTConfiguration.mpsServer : '';
|
||||
} catch (err) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue