diff --git a/app/docker/components/datatables/nodes-datatable/nodesDatatable.html b/app/docker/components/datatables/nodes-datatable/nodesDatatable.html deleted file mode 100644 index 645db63f8..000000000 --- a/app/docker/components/datatables/nodes-datatable/nodesDatatable.html +++ /dev/null @@ -1,188 +0,0 @@ -
- - -
-
-
- -
- {{ $ctrl.titleText }} -
- -
- - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- {{ item.Name || item.Hostname }} - {{ item.Name || item.Hostname }} - {{ item.Role }}{{ item.CPUs / 1000000000 }}{{ item.Memory | humansize }}{{ item.EngineVersion }}{{ item.Addr }}{{ item.Status }}{{ item.Availability }}
Loading...
No node available.
-
- -
-
-
diff --git a/app/docker/components/datatables/nodes-datatable/nodesDatatable.js b/app/docker/components/datatables/nodes-datatable/nodesDatatable.js deleted file mode 100644 index 1b6de9fde..000000000 --- a/app/docker/components/datatables/nodes-datatable/nodesDatatable.js +++ /dev/null @@ -1,15 +0,0 @@ -angular.module('portainer.docker').component('nodesDatatable', { - templateUrl: './nodesDatatable.html', - controller: 'GenericDatatableController', - bindings: { - titleText: '@', - titleIcon: '@', - dataset: '<', - tableKey: '@', - orderBy: '@', - reverseOrder: '<', - showIpAddressColumn: '<', - accessToNodeDetails: '<', - refreshCallback: '<', - }, -}); diff --git a/app/docker/filters/filters.js b/app/docker/filters/filters.js index 4f5944e25..9839394d9 100644 --- a/app/docker/filters/filters.js +++ b/app/docker/filters/filters.js @@ -1,5 +1,5 @@ import _ from 'lodash-es'; -import { joinCommand, taskStatusBadge, nodeStatusBadge, trimSHA } from './utils'; +import { joinCommand, taskStatusBadge, nodeStatusBadge, trimSHA, dockerNodeAvailabilityBadge } from './utils'; function includeString(text, values) { return values.some(function (val) { @@ -76,17 +76,7 @@ angular }; }) .filter('nodestatusbadge', () => nodeStatusBadge) - .filter('dockerNodeAvailabilityBadge', function () { - 'use strict'; - return function (text) { - if (text === 'pause') { - return 'warning'; - } else if (text === 'drain') { - return 'danger'; - } - return 'success'; - }; - }) + .filter('dockerNodeAvailabilityBadge', () => dockerNodeAvailabilityBadge) .filter('trimcontainername', function () { 'use strict'; return function (name) { diff --git a/app/docker/filters/utils.ts b/app/docker/filters/utils.ts index 144df280e..05723b239 100644 --- a/app/docker/filters/utils.ts +++ b/app/docker/filters/utils.ts @@ -1,4 +1,4 @@ -import { NodeStatus, TaskState } from 'docker-types/generated/1.41'; +import { NodeStatus, TaskState, NodeSpec } from 'docker-types/generated/1.41'; import _ from 'lodash'; export function trimSHA(imageName: string) { @@ -61,3 +61,15 @@ export function nodeStatusBadge(text: NodeStatus['State']) { return 'success'; } + +export function dockerNodeAvailabilityBadge(text: NodeSpec['Availability']) { + if (text === 'pause') { + return 'warning'; + } + + if (text === 'drain') { + return 'danger'; + } + + return 'success'; +} diff --git a/app/docker/react/components/index.ts b/app/docker/react/components/index.ts index 4e466be12..bbf37a899 100644 --- a/app/docker/react/components/index.ts +++ b/app/docker/react/components/index.ts @@ -28,12 +28,14 @@ import { StacksDatatable } from '@/react/docker/stacks/ListView/StacksDatatable' import { containersModule } from './containers'; import { servicesModule } from './services'; import { networksModule } from './networks'; +import { swarmModule } from './swarm'; const ngModule = angular .module('portainer.docker.react.components', [ containersModule, servicesModule, networksModule, + swarmModule, ]) .component('dockerfileDetails', r2a(DockerfileDetails, ['image'])) .component('dockerHealthStatus', r2a(HealthStatus, ['health'])) diff --git a/app/docker/react/components/swarm.ts b/app/docker/react/components/swarm.ts new file mode 100644 index 000000000..b96f9a6f8 --- /dev/null +++ b/app/docker/react/components/swarm.ts @@ -0,0 +1,17 @@ +import angular from 'angular'; + +import { r2a } from '@/react-tools/react2angular'; +import { withUIRouter } from '@/react-tools/withUIRouter'; +import { NodesDatatable } from '@/react/docker/swarm/SwarmView/NodesDatatable'; + +export const swarmModule = angular + .module('portainer.docker.react.components.swarm', []) + .component( + 'nodesDatatable', + r2a(withUIRouter(NodesDatatable), [ + 'dataset', + 'isIpColumnVisible', + 'haveAccessToNode', + 'onRefresh', + ]) + ).name; diff --git a/app/docker/views/swarm/swarm.html b/app/docker/views/swarm/swarm.html index cc577b785..0aab190b9 100644 --- a/app/docker/views/swarm/swarm.html +++ b/app/docker/views/swarm/swarm.html @@ -37,17 +37,4 @@ -
-
- -
-
+ diff --git a/app/react/docker/networks/CreateView/MacvlanNodesSelector/MacvlanNodesSelector.tsx b/app/react/docker/networks/CreateView/MacvlanNodesSelector/MacvlanNodesSelector.tsx index fd071f209..7a3f7e8ac 100644 --- a/app/react/docker/networks/CreateView/MacvlanNodesSelector/MacvlanNodesSelector.tsx +++ b/app/react/docker/networks/CreateView/MacvlanNodesSelector/MacvlanNodesSelector.tsx @@ -9,7 +9,7 @@ import { mergeOptions } from '@@/datatables/extend-options/mergeOptions'; import { withMeta } from '@@/datatables/extend-options/withMeta'; import { withControlledSelected } from '@@/datatables/extend-options/withControlledSelected'; -import { useColumns } from './columns'; +import { useColumns } from './useColumns'; const tableKey = 'macvlan-nodes-selector'; const store = createPersistedStore(tableKey); @@ -41,7 +41,7 @@ export function MacvlanNodesSelector({ settingsManager={tableState} extendTableOptions={mergeOptions( withMeta({ - table: 'macvlan-nodes', + table: 'nodes', haveAccessToNode, }), withControlledSelected( diff --git a/app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/index.ts b/app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/index.ts deleted file mode 100644 index cbbf34268..000000000 --- a/app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import _ from 'lodash'; - -import { columnHelper } from './column-helper'; -import { name } from './name'; -import { status } from './status'; - -export function useColumns(isIpColumnVisible: boolean) { - return _.compact([ - name, - columnHelper.accessor('Role', {}), - columnHelper.accessor('EngineVersion', { - header: 'Engine', - }), - isIpColumnVisible && - columnHelper.accessor('Addr', { - header: 'IP Address', - }), - status, - ]); -} diff --git a/app/react/docker/networks/CreateView/MacvlanNodesSelector/useColumns.ts b/app/react/docker/networks/CreateView/MacvlanNodesSelector/useColumns.ts new file mode 100644 index 000000000..0279e251d --- /dev/null +++ b/app/react/docker/networks/CreateView/MacvlanNodesSelector/useColumns.ts @@ -0,0 +1,17 @@ +import { useMemo } from 'react'; +import _ from 'lodash'; + +import { + engine, + ip, + role, + name, + status, +} from '@/react/docker/swarm/SwarmView/NodesDatatable/columns'; + +export function useColumns(isIpColumnVisible: boolean) { + return useMemo( + () => _.compact([name, role, engine, isIpColumnVisible && ip, status]), + [isIpColumnVisible] + ); +} diff --git a/app/react/docker/swarm/SwarmView/NodesDatatable/NodesDatatable.tsx b/app/react/docker/swarm/SwarmView/NodesDatatable/NodesDatatable.tsx new file mode 100644 index 000000000..e339f991a --- /dev/null +++ b/app/react/docker/swarm/SwarmView/NodesDatatable/NodesDatatable.tsx @@ -0,0 +1,70 @@ +import { Trello } from 'lucide-react'; + +import { NodeViewModel } from '@/docker/models/node'; + +import { Datatable, TableSettingsMenu } from '@@/datatables'; +import { + BasicTableSettings, + RefreshableTableSettings, + createPersistedStore, + refreshableSettings, +} from '@@/datatables/types'; +import { useTableState } from '@@/datatables/useTableState'; +import { useRepeater } from '@@/datatables/useRepeater'; +import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh'; +import { withMeta } from '@@/datatables/extend-options/withMeta'; + +import { useColumns } from './columns'; + +const tableKey = 'nodes'; + +interface TableSettings extends BasicTableSettings, RefreshableTableSettings {} + +const store = createPersistedStore( + tableKey, + undefined, + (set) => ({ + ...refreshableSettings(set), + }) +); + +export function NodesDatatable({ + dataset, + isIpColumnVisible, + haveAccessToNode, + onRefresh, +}: { + dataset?: Array; + isIpColumnVisible: boolean; + haveAccessToNode: boolean; + onRefresh(): Promise; +}) { + const columns = useColumns(isIpColumnVisible); + const tableState = useTableState(store, tableKey); + useRepeater(tableState.autoRefreshRate, onRefresh); + + return ( + + disableSelect + title="Nodes" + titleIcon={Trello} + columns={columns} + dataset={dataset || []} + isLoading={!dataset} + emptyContentLabel="No node available" + settingsManager={tableState} + extendTableOptions={withMeta({ + table: 'nodes', + haveAccessToNode, + })} + renderTableSettings={() => ( + + tableState.setAutoRefreshRate(value)} + /> + + )} + /> + ); +} diff --git a/app/react/docker/swarm/SwarmView/NodesDatatable/columns/availability.tsx b/app/react/docker/swarm/SwarmView/NodesDatatable/columns/availability.tsx new file mode 100644 index 000000000..0da94e91a --- /dev/null +++ b/app/react/docker/swarm/SwarmView/NodesDatatable/columns/availability.tsx @@ -0,0 +1,29 @@ +import clsx from 'clsx'; + +import { NodeViewModel } from '@/docker/models/node'; + +import { columnHelper } from './column-helper'; + +export const availability = columnHelper.accessor('Availability', { + header: 'Availability', + cell({ getValue }) { + const value = getValue(); + return ( + + {value} + + ); + }, +}); + +export function badgeClass(text: NodeViewModel['Availability']) { + if (text === 'pause') { + return 'warning'; + } + + if (text === 'drain') { + return 'danger'; + } + + return 'success'; +} diff --git a/app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/column-helper.ts b/app/react/docker/swarm/SwarmView/NodesDatatable/columns/column-helper.ts similarity index 100% rename from app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/column-helper.ts rename to app/react/docker/swarm/SwarmView/NodesDatatable/columns/column-helper.ts diff --git a/app/react/docker/swarm/SwarmView/NodesDatatable/columns/index.ts b/app/react/docker/swarm/SwarmView/NodesDatatable/columns/index.ts new file mode 100644 index 000000000..0844321a2 --- /dev/null +++ b/app/react/docker/swarm/SwarmView/NodesDatatable/columns/index.ts @@ -0,0 +1,53 @@ +import _ from 'lodash'; +import { useMemo } from 'react'; + +import { humanize } from '@/portainer/filters/filters'; + +import { columnHelper } from './column-helper'; +import { name } from './name'; +import { status } from './status'; +import { availability } from './availability'; + +export { name, status }; + +export const role = columnHelper.accessor('Role', {}); + +export const engine = columnHelper.accessor('EngineVersion', { + header: 'Engine', +}); + +export const ip = columnHelper.accessor('Addr', { + header: 'IP Address', +}); + +export const cpu = columnHelper.accessor( + (item) => (item.CPUs ? item.CPUs / 1000000000 : 0), + { + header: 'CPU', + } +); + +export const memory = columnHelper.accessor('Memory', { + header: 'Memory', + cell({ getValue }) { + const value = getValue(); + return humanize(value); + }, +}); + +export function useColumns(isIpColumnVisible: boolean) { + return useMemo( + () => + _.compact([ + name, + role, + cpu, + memory, + engine, + isIpColumnVisible && ip, + status, + availability, + ]), + [isIpColumnVisible] + ); +} diff --git a/app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/name.tsx b/app/react/docker/swarm/SwarmView/NodesDatatable/columns/name.tsx similarity index 100% rename from app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/name.tsx rename to app/react/docker/swarm/SwarmView/NodesDatatable/columns/name.tsx diff --git a/app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/status.tsx b/app/react/docker/swarm/SwarmView/NodesDatatable/columns/status.tsx similarity index 92% rename from app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/status.tsx rename to app/react/docker/swarm/SwarmView/NodesDatatable/columns/status.tsx index 88642d7ac..a61e35bca 100644 --- a/app/react/docker/networks/CreateView/MacvlanNodesSelector/columns/status.tsx +++ b/app/react/docker/swarm/SwarmView/NodesDatatable/columns/status.tsx @@ -5,7 +5,7 @@ import { nodeStatusBadge } from '@/docker/filters/utils'; import { columnHelper } from './column-helper'; export const status = columnHelper.accessor('Status', { - cell: ({ getValue }) => { + cell({ getValue }) { const value = getValue(); return ( diff --git a/app/react/docker/swarm/SwarmView/NodesDatatable/index.ts b/app/react/docker/swarm/SwarmView/NodesDatatable/index.ts new file mode 100644 index 000000000..f5e6969ba --- /dev/null +++ b/app/react/docker/swarm/SwarmView/NodesDatatable/index.ts @@ -0,0 +1 @@ +export { NodesDatatable } from './NodesDatatable'; diff --git a/app/react/docker/networks/CreateView/MacvlanNodesSelector/types.ts b/app/react/docker/swarm/SwarmView/NodesDatatable/types.ts similarity index 79% rename from app/react/docker/networks/CreateView/MacvlanNodesSelector/types.ts rename to app/react/docker/swarm/SwarmView/NodesDatatable/types.ts index 290c64a5d..c5f14029d 100644 --- a/app/react/docker/networks/CreateView/MacvlanNodesSelector/types.ts +++ b/app/react/docker/swarm/SwarmView/NodesDatatable/types.ts @@ -3,12 +3,12 @@ import { TableMeta as BaseTableMeta } from '@tanstack/react-table'; import { NodeViewModel } from '@/docker/models/node'; export type TableMeta = BaseTableMeta & { - table: 'macvlan-nodes'; + table: 'nodes'; haveAccessToNode: boolean; }; export function isTableMeta( meta?: BaseTableMeta ): meta is TableMeta { - return !!meta && meta.table === 'macvlan-nodes'; + return !!meta && meta.table === 'nodes'; }