From c3d266931fd461282a11c12eca4c61df989e2966 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Thu, 7 Sep 2023 15:19:03 +0100 Subject: [PATCH] refactor(docker/services): convert service tasks table to react [EE-4674] (#10188) --- .../serviceTasksDatatable.html | 109 ------------------ .../serviceTasksDatatable.js | 15 --- .../serviceTasksDatatableController.js | 94 --------------- .../services-datatable/servicesDatatable.html | 11 +- .../tasks-datatable/tasksDatatable.html | 8 +- app/docker/filters/filters.js | 19 +-- app/docker/filters/utils.ts | 36 ++++++ app/docker/models/task.js | 14 --- app/docker/models/task.ts | 36 ++++++ app/docker/react/components/index.ts | 7 +- app/docker/react/components/services.ts | 21 ++++ .../components/datatables/NestedDatatable.tsx | 11 +- .../ContainerQuickActions.tsx | 69 +++-------- .../docker/proxy/queries/nodes/build-url.ts | 15 +++ .../docker/proxy/queries/nodes/query-keys.ts | 8 ++ .../docker/proxy/queries/nodes/useNodes.ts | 21 ++++ .../TasksDatatable/TasksDatatable.tsx | 21 ++++ .../TasksDatatable/columns/actions.tsx | 45 ++++++++ .../TasksDatatable/columns/helper.ts | 5 + .../TasksDatatable/columns/index.ts | 19 +++ .../TasksDatatable/columns/node.tsx | 32 +++++ .../TasksDatatable/columns/status.tsx | 27 +++++ .../TasksDatatable/columns/task.tsx | 47 ++++++++ .../ServicesDatatable/TasksDatatable/index.ts | 1 + .../ServicesDatatable/TasksDatatable/types.ts | 6 + .../services/common/TaskTableQuickActions.tsx | 46 ++++++++ 26 files changed, 421 insertions(+), 322 deletions(-) delete mode 100644 app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html delete mode 100644 app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.js delete mode 100644 app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatableController.js delete mode 100644 app/docker/models/task.js create mode 100644 app/docker/models/task.ts create mode 100644 app/docker/react/components/services.ts create mode 100644 app/react/docker/proxy/queries/nodes/build-url.ts create mode 100644 app/react/docker/proxy/queries/nodes/query-keys.ts create mode 100644 app/react/docker/proxy/queries/nodes/useNodes.ts create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/TasksDatatable.tsx create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/actions.tsx create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/helper.ts create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/index.ts create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/node.tsx create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/status.tsx create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/task.tsx create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/index.ts create mode 100644 app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/types.ts create mode 100644 app/react/docker/services/common/TaskTableQuickActions.tsx diff --git a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html deleted file mode 100644 index 734414b09..000000000 --- a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.html +++ /dev/null @@ -1,109 +0,0 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - Filter - - - Filter - - - - -
-
TaskActions - - - - - -
- {{ item.Status.State }} - - {{ item.Id }} - {{ - item.Id - }} - - - - {{ item.Slot ? item.Slot : '-' }}{{ item.NodeId | tasknodename: $ctrl.nodes }}{{ item.Updated | getisodate }}
No task matching filter.
-
diff --git a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.js b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.js deleted file mode 100644 index 8bfdd2a72..000000000 --- a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatable.js +++ /dev/null @@ -1,15 +0,0 @@ -angular.module('portainer.docker').component('serviceTasksDatatable', { - templateUrl: './serviceTasksDatatable.html', - controller: 'ServiceTasksDatatableController', - bindings: { - dataset: '<', - serviceId: '<', - tableKey: '@', - orderBy: '@', - reverseOrder: '<', - nodes: '<', - agentProxy: '<', - textFilter: '=', - showTaskLogsButton: '<', - }, -}); diff --git a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatableController.js b/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatableController.js deleted file mode 100644 index 437732bdc..000000000 --- a/app/docker/components/datatables/service-tasks-datatable/serviceTasksDatatableController.js +++ /dev/null @@ -1,94 +0,0 @@ -import _ from 'lodash-es'; - -angular.module('portainer.docker').controller('ServiceTasksDatatableController', [ - '$scope', - '$controller', - 'DatatableService', - function ($scope, $controller, DatatableService) { - angular.extend(this, $controller('GenericDatatableController', { $scope: $scope })); - - var ctrl = this; - - this.state = Object.assign(this.state, { - showQuickActionStats: true, - showQuickActionLogs: true, - showQuickActionConsole: true, - showQuickActionInspect: true, - showQuickActionExec: true, - showQuickActionAttach: false, - }); - - this.filters = { - state: { - open: false, - enabled: false, - values: [], - }, - }; - - this.applyFilters = function (item) { - var filters = ctrl.filters; - for (var i = 0; i < filters.state.values.length; i++) { - var filter = filters.state.values[i]; - if (item.Status.State === filter.label && filter.display) { - return true; - } - } - return false; - }; - - this.onStateFilterChange = function () { - var filters = this.filters.state.values; - var filtered = false; - for (var i = 0; i < filters.length; i++) { - var filter = filters[i]; - if (!filter.display) { - filtered = true; - } - } - this.filters.state.enabled = filtered; - }; - - this.prepareTableFromDataset = function () { - var availableStateFilters = []; - for (var i = 0; i < this.dataset.length; i++) { - var item = this.dataset[i]; - availableStateFilters.push({ label: item.Status.State, display: true }); - } - this.filters.state.values = _.uniqBy(availableStateFilters, 'label'); - }; - - this.$onInit = function () { - this.setDefaults(); - this.prepareTableFromDataset(); - - this.state.orderBy = this.orderBy; - var storedOrder = DatatableService.getDataTableOrder(this.tableKey); - if (storedOrder !== null) { - this.state.reverseOrder = storedOrder.reverse; - this.state.orderBy = storedOrder.orderBy; - } - - var textFilter = DatatableService.getDataTableTextFilters(this.tableKey); - if (textFilter !== null) { - this.state.textFilter = textFilter; - this.onTextFilterChange(); - } - - var storedFilters = DatatableService.getDataTableFilters(this.tableKey); - if (storedFilters !== null) { - this.filters = storedFilters; - } - if (this.filters && this.filters.state) { - this.filters.state.open = false; - } - - var storedSettings = DatatableService.getDataTableSettings(this.tableKey); - if (storedSettings !== null) { - this.settings = storedSettings; - this.settings.open = false; - } - this.onSettingsRepeaterChange(); - }; - }, -]); diff --git a/app/docker/components/datatables/services-datatable/servicesDatatable.html b/app/docker/components/datatables/services-datatable/servicesDatatable.html index 7a9ff7969..fb014777c 100644 --- a/app/docker/components/datatables/services-datatable/servicesDatatable.html +++ b/app/docker/components/datatables/services-datatable/servicesDatatable.html @@ -210,16 +210,7 @@ - + diff --git a/app/docker/components/datatables/tasks-datatable/tasksDatatable.html b/app/docker/components/datatables/tasks-datatable/tasksDatatable.html index f53d42405..fed57be91 100644 --- a/app/docker/components/datatables/tasks-datatable/tasksDatatable.html +++ b/app/docker/components/datatables/tasks-datatable/tasksDatatable.html @@ -89,13 +89,7 @@ > - + = []) { return command.join(' '); } + +export function taskStatusBadge(text?: TaskState) { + const status = _.toLower(text); + if ( + [ + 'new', + 'allocated', + 'assigned', + 'accepted', + 'preparing', + 'ready', + 'starting', + 'remove', + ].includes(status) + ) { + return 'info'; + } + + if (['pending'].includes(status)) { + return 'warning'; + } + + if (['shutdown', 'failed', 'rejected', 'orphaned'].includes(status)) { + return 'danger'; + } + + if (['complete'].includes(status)) { + return 'primary'; + } + + if (['running'].includes(status)) { + return 'success'; + } + return 'default'; +} diff --git a/app/docker/models/task.js b/app/docker/models/task.js deleted file mode 100644 index 6e16b3a56..000000000 --- a/app/docker/models/task.js +++ /dev/null @@ -1,14 +0,0 @@ -export function TaskViewModel(data) { - this.Id = data.ID; - this.Created = data.CreatedAt; - this.Updated = data.UpdatedAt; - this.Slot = data.Slot; - this.Spec = data.Spec; - this.Status = data.Status; - this.DesiredState = data.DesiredState; - this.ServiceId = data.ServiceID; - this.NodeId = data.NodeID; - if (data.Status && data.Status.ContainerStatus && data.Status.ContainerStatus.ContainerID) { - this.ContainerId = data.Status.ContainerStatus.ContainerID; - } -} diff --git a/app/docker/models/task.ts b/app/docker/models/task.ts new file mode 100644 index 000000000..c67a41171 --- /dev/null +++ b/app/docker/models/task.ts @@ -0,0 +1,36 @@ +import { Task, TaskSpec, TaskState } from 'docker-types/generated/1.41'; + +export class TaskViewModel { + Id: string; + + Created: string; + + Updated: string; + + Slot: number; + + Spec?: TaskSpec; + + Status: Task['Status']; + + DesiredState: TaskState; + + ServiceId: string; + + NodeId: string; + + ContainerId: string = ''; + + constructor(data: Task) { + this.Id = data.ID || ''; + this.Created = data.CreatedAt || ''; + this.Updated = data.UpdatedAt || ''; + this.Slot = data.Slot || 0; + this.Spec = data.Spec; + this.Status = data.Status; + this.DesiredState = data.DesiredState || 'pending'; + this.ServiceId = data.ServiceID || ''; + this.NodeId = data.NodeID || ''; + this.ContainerId = data.Status?.ContainerStatus?.ContainerID || ''; + } +} diff --git a/app/docker/react/components/index.ts b/app/docker/react/components/index.ts index f7c6ecbb5..1edeeb660 100644 --- a/app/docker/react/components/index.ts +++ b/app/docker/react/components/index.ts @@ -25,9 +25,13 @@ import { ScaleServiceButton } from '@/react/docker/services/ListView/ServicesDat import { SecretsDatatable } from '@/react/docker/secrets/ListView/SecretsDatatable'; import { containersModule } from './containers'; +import { servicesModule } from './services'; const ngModule = angular - .module('portainer.docker.react.components', [containersModule]) + .module('portainer.docker.react.components', [ + containersModule, + servicesModule, + ]) .component('dockerfileDetails', r2a(DockerfileDetails, ['image'])) .component('dockerHealthStatus', r2a(HealthStatus, ['health'])) .component( @@ -37,7 +41,6 @@ const ngModule = angular 'nodeName', 'state', 'status', - 'taskId', ]) ) .component('templateListDropdown', TemplateListDropdownAngular) diff --git a/app/docker/react/components/services.ts b/app/docker/react/components/services.ts new file mode 100644 index 000000000..ec4634823 --- /dev/null +++ b/app/docker/react/components/services.ts @@ -0,0 +1,21 @@ +import angular from 'angular'; + +import { r2a } from '@/react-tools/react2angular'; +import { withUIRouter } from '@/react-tools/withUIRouter'; +import { TasksDatatable } from '@/react/docker/services/ListView/ServicesDatatable/TasksDatatable'; +import { withCurrentUser } from '@/react-tools/withCurrentUser'; +import { TaskTableQuickActions } from '@/react/docker/services/common/TaskTableQuickActions'; + +export const servicesModule = angular + .module('portainer.docker.react.components.services', []) + .component( + 'dockerServiceTasksDatatable', + r2a(withUIRouter(withCurrentUser(TasksDatatable)), ['dataset', 'search']) + ) + .component( + 'dockerTaskTableQuickActions', + r2a(withUIRouter(withCurrentUser(TaskTableQuickActions)), [ + 'state', + 'taskId', + ]) + ).name; diff --git a/app/react/components/datatables/NestedDatatable.tsx b/app/react/components/datatables/NestedDatatable.tsx index 1b0eaaa3b..6569435d5 100644 --- a/app/react/components/datatables/NestedDatatable.tsx +++ b/app/react/components/datatables/NestedDatatable.tsx @@ -23,6 +23,11 @@ interface Props { initialTableState?: Partial; isLoading?: boolean; initialSortBy?: BasicTableSettings['sortBy']; + + /** + * keyword to filter by + */ + search?: string; } export function NestedDatatable({ @@ -33,6 +38,7 @@ export function NestedDatatable({ initialTableState = {}, isLoading, initialSortBy, + search, }: Props) { const tableInstance = useReactTable({ columns, @@ -45,6 +51,9 @@ export function NestedDatatable({ enableColumnFilter: false, enableHiding: false, }, + state: { + globalFilter: search, + }, getRowId, autoResetExpanded: false, getCoreRowModel: getCoreRowModel(), @@ -55,7 +64,7 @@ export function NestedDatatable({ return ( - + tableInstance={tableInstance} isLoading={isLoading} diff --git a/app/react/docker/containers/components/ContainerQuickActions/ContainerQuickActions.tsx b/app/react/docker/containers/components/ContainerQuickActions/ContainerQuickActions.tsx index 81ac473d8..891f40a7a 100644 --- a/app/react/docker/containers/components/ContainerQuickActions/ContainerQuickActions.tsx +++ b/app/react/docker/containers/components/ContainerQuickActions/ContainerQuickActions.tsx @@ -9,7 +9,7 @@ import { Link } from '@@/Link'; import styles from './ContainerQuickActions.module.css'; -interface QuickActionsState { +export interface QuickActionsState { showQuickActionAttach: boolean; showQuickActionExec: boolean; showQuickActionInspect: boolean; @@ -17,31 +17,25 @@ interface QuickActionsState { showQuickActionStats: boolean; } -interface Props { - taskId?: string; - containerId?: string; - nodeName: string; - state: QuickActionsState; - status: ContainerStatus; -} - export function ContainerQuickActions({ - taskId, + status, containerId, nodeName, state, - status, -}: Props) { - if (taskId) { - return ; - } - - const isActive = [ - ContainerStatus.Starting, - ContainerStatus.Running, - ContainerStatus.Healthy, - ContainerStatus.Unhealthy, - ].includes(status); +}: { + containerId: string; + nodeName: string; + status: ContainerStatus; + state: QuickActionsState; +}) { + const isActive = + !!status && + [ + ContainerStatus.Starting, + ContainerStatus.Running, + ContainerStatus.Healthy, + ContainerStatus.Unhealthy, + ].includes(status); return (
@@ -107,34 +101,3 @@ export function ContainerQuickActions({
); } - -interface TaskProps { - taskId: string; - state: QuickActionsState; -} - -function TaskQuickActions({ taskId, state }: TaskProps) { - return ( -
- {state.showQuickActionLogs && ( - - - - - - )} - - {state.showQuickActionInspect && ( - - - - - - )} -
- ); -} diff --git a/app/react/docker/proxy/queries/nodes/build-url.ts b/app/react/docker/proxy/queries/nodes/build-url.ts new file mode 100644 index 000000000..2c422d18e --- /dev/null +++ b/app/react/docker/proxy/queries/nodes/build-url.ts @@ -0,0 +1,15 @@ +import { EnvironmentId } from '@/react/portainer/environments/types'; + +import { buildUrl as buildProxyUrl } from '../build-url'; + +export function buildUrl( + environmentId: EnvironmentId, + action?: string, + subAction = '' +) { + return buildProxyUrl( + environmentId, + 'nodes', + subAction ? `${action}/${subAction}` : action + ); +} diff --git a/app/react/docker/proxy/queries/nodes/query-keys.ts b/app/react/docker/proxy/queries/nodes/query-keys.ts new file mode 100644 index 000000000..3f599e1d8 --- /dev/null +++ b/app/react/docker/proxy/queries/nodes/query-keys.ts @@ -0,0 +1,8 @@ +import { EnvironmentId } from '@/react/portainer/environments/types'; + +import { queryKeys as proxyQueryKeys } from '../query-keys'; + +export const queryKeys = { + base: (environmentId: EnvironmentId) => + [...proxyQueryKeys.base(environmentId), 'nodes'] as const, +}; diff --git a/app/react/docker/proxy/queries/nodes/useNodes.ts b/app/react/docker/proxy/queries/nodes/useNodes.ts new file mode 100644 index 000000000..cc52ffae1 --- /dev/null +++ b/app/react/docker/proxy/queries/nodes/useNodes.ts @@ -0,0 +1,21 @@ +import { Node } from 'docker-types/generated/1.41'; +import { useQuery } from 'react-query'; + +import axios, { parseAxiosError } from '@/portainer/services/axios'; +import { EnvironmentId } from '@/react/portainer/environments/types'; + +import { buildUrl } from './build-url'; +import { queryKeys } from './query-keys'; + +export function useNodes(environmentId: EnvironmentId) { + return useQuery(queryKeys.base(environmentId), () => getNodes(environmentId)); +} + +async function getNodes(environmentId: EnvironmentId) { + try { + const { data } = await axios.get>(buildUrl(environmentId)); + return data; + } catch (error) { + throw parseAxiosError(error, 'Unable to retrieve nodes'); + } +} diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/TasksDatatable.tsx b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/TasksDatatable.tsx new file mode 100644 index 000000000..8f1e906fd --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/TasksDatatable.tsx @@ -0,0 +1,21 @@ +import { NestedDatatable } from '@@/datatables/NestedDatatable'; + +import { columns } from './columns'; +import { DecoratedTask } from './types'; + +export function TasksDatatable({ + dataset, + search, +}: { + dataset: DecoratedTask[]; + search?: string; +}) { + return ( + + ); +} diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/actions.tsx b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/actions.tsx new file mode 100644 index 000000000..fe672dc74 --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/actions.tsx @@ -0,0 +1,45 @@ +import { CellContext } from '@tanstack/react-table'; + +import { ContainerQuickActions } from '@/react/docker/containers/components/ContainerQuickActions'; +import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment'; +import { isAgentEnvironment } from '@/react/portainer/environments/utils'; +import { QuickActionsState } from '@/react/docker/containers/components/ContainerQuickActions/ContainerQuickActions'; +import { TaskTableQuickActions } from '@/react/docker/services/common/TaskTableQuickActions'; + +import { DecoratedTask } from '../types'; + +import { columnHelper } from './helper'; + +export const actions = columnHelper.display({ + header: 'Actions', + cell: Cell, +}); + +function Cell({ + row: { original: item }, +}: CellContext) { + const environmentQuery = useCurrentEnvironment(); + + if (!environmentQuery.data) { + return null; + } + const state: QuickActionsState = { + showQuickActionAttach: true, + showQuickActionExec: true, + showQuickActionInspect: true, + showQuickActionLogs: true, + showQuickActionStats: true, + }; + const isAgent = isAgentEnvironment(environmentQuery.data.Type); + + return isAgent && item.Container ? ( + + ) : ( + + ); +} diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/helper.ts b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/helper.ts new file mode 100644 index 000000000..34343b077 --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/helper.ts @@ -0,0 +1,5 @@ +import { createColumnHelper } from '@tanstack/react-table'; + +import { DecoratedTask } from '../types'; + +export const columnHelper = createColumnHelper(); diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/index.ts b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/index.ts new file mode 100644 index 000000000..aaca2fa3f --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/index.ts @@ -0,0 +1,19 @@ +import { isoDate } from '@/portainer/filters/filters'; + +import { actions } from './actions'; +import { columnHelper } from './helper'; +import { node } from './node'; +import { status } from './status'; +import { task } from './task'; + +export const columns = [ + status, + task, + actions, + columnHelper.accessor((item) => item.Slot || '-', { header: 'Slot' }), + node, + columnHelper.accessor('Updated', { + header: 'Last Update', + cell: ({ getValue }) => isoDate(getValue()), + }), +]; diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/node.tsx b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/node.tsx new file mode 100644 index 000000000..56ae02877 --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/node.tsx @@ -0,0 +1,32 @@ +import { Node } from 'docker-types/generated/1.41'; +import { CellContext } from '@tanstack/react-table'; + +import { useNodes } from '@/react/docker/proxy/queries/nodes/useNodes'; +import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; + +import { DecoratedTask } from '../types'; + +import { columnHelper } from './helper'; + +export const node = columnHelper.accessor('NodeId', { + header: 'Node', + cell: Cell, +}); + +function Cell({ getValue }: CellContext) { + const environmentId = useEnvironmentId(); + + const nodesQuery = useNodes(environmentId); + + const nodes = nodesQuery.data || []; + return getNodeName(getValue(), nodes); +} + +function getNodeName(nodeId: string, nodes: Array) { + const node = nodes.find((node) => node.ID === nodeId); + if (node?.Description?.Hostname) { + return node.Description.Hostname; + } + + return ''; +} diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/status.tsx b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/status.tsx new file mode 100644 index 000000000..7f1bc39ea --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/status.tsx @@ -0,0 +1,27 @@ +import clsx from 'clsx'; + +import { taskStatusBadge } from '@/docker/filters/utils'; + +import { multiple } from '@@/datatables/filter-types'; +import { filterHOC } from '@@/datatables/Filter'; + +import { columnHelper } from './helper'; + +export const status = columnHelper.accessor((item) => item.Status?.State, { + header: 'Status', + enableColumnFilter: true, + filterFn: multiple, + meta: { + filter: filterHOC('Filter by state'), + width: 100, + }, + cell({ getValue }) { + const value = getValue(); + + return ( + + {value} + + ); + }, +}); diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/task.tsx b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/task.tsx new file mode 100644 index 000000000..1a94f5947 --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/columns/task.tsx @@ -0,0 +1,47 @@ +import { CellContext } from '@tanstack/react-table'; + +import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment'; +import { isAgentEnvironment } from '@/react/portainer/environments/utils'; + +import { Link } from '@@/Link'; + +import { DecoratedTask } from '../types'; + +import { columnHelper } from './helper'; + +export const task = columnHelper.accessor('Id', { + header: 'Task', + cell: Cell, +}); + +function Cell({ + getValue, + row: { original: item }, +}: CellContext) { + const environmentQuery = useCurrentEnvironment(); + + if (!environmentQuery.data) { + return null; + } + + const value = getValue(); + const isAgent = isAgentEnvironment(environmentQuery.data.Type); + + return isAgent && item.Container ? ( + + {value} + + ) : ( + + {value} + + ); +} diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/index.ts b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/index.ts new file mode 100644 index 000000000..f95acf3ac --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/index.ts @@ -0,0 +1 @@ +export { TasksDatatable } from './TasksDatatable'; diff --git a/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/types.ts b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/types.ts new file mode 100644 index 000000000..fdff024a9 --- /dev/null +++ b/app/react/docker/services/ListView/ServicesDatatable/TasksDatatable/types.ts @@ -0,0 +1,6 @@ +import { TaskViewModel } from '@/docker/models/task'; +import { DockerContainer } from '@/react/docker/containers/types'; + +export type DecoratedTask = TaskViewModel & { + Container?: DockerContainer; +}; diff --git a/app/react/docker/services/common/TaskTableQuickActions.tsx b/app/react/docker/services/common/TaskTableQuickActions.tsx new file mode 100644 index 000000000..367c4bc27 --- /dev/null +++ b/app/react/docker/services/common/TaskTableQuickActions.tsx @@ -0,0 +1,46 @@ +import { FileText, Info } from 'lucide-react'; + +import { Authorized } from '@/react/hooks/useUser'; + +import { Icon } from '@@/Icon'; +import { Link } from '@@/Link'; + +interface State { + showQuickActionInspect: boolean; + showQuickActionLogs: boolean; +} + +export function TaskTableQuickActions({ + taskId, + state = { + showQuickActionInspect: true, + showQuickActionLogs: true, + }, +}: { + taskId: string; + state?: State; +}) { + return ( +
+ {state.showQuickActionLogs && ( + + + + + + )} + + {state.showQuickActionInspect && ( + + + + + + )} +
+ ); +}