mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 00:09:40 +02:00
refactor(docker/services): convert service tasks table to react [EE-4674] (#10188)
This commit is contained in:
parent
c47a804c97
commit
c3d266931f
26 changed files with 421 additions and 322 deletions
|
@ -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 <TaskQuickActions taskId={taskId} state={state} />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={clsx('space-x-1', styles.root)}>
|
||||
|
@ -107,34 +101,3 @@ export function ContainerQuickActions({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TaskProps {
|
||||
taskId: string;
|
||||
state: QuickActionsState;
|
||||
}
|
||||
|
||||
function TaskQuickActions({ taskId, state }: TaskProps) {
|
||||
return (
|
||||
<div className={clsx('space-x-1', styles.root)}>
|
||||
{state.showQuickActionLogs && (
|
||||
<Authorized authorizations="DockerTaskLogs">
|
||||
<Link
|
||||
to="docker.tasks.task.logs"
|
||||
params={{ id: taskId }}
|
||||
title="Logs"
|
||||
>
|
||||
<Icon icon={FileText} className="space-right" />
|
||||
</Link>
|
||||
</Authorized>
|
||||
)}
|
||||
|
||||
{state.showQuickActionInspect && (
|
||||
<Authorized authorizations="DockerTaskInspect">
|
||||
<Link to="docker.tasks.task" params={{ id: taskId }} title="Inspect">
|
||||
<Icon icon={Info} className="space-right" />
|
||||
</Link>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
15
app/react/docker/proxy/queries/nodes/build-url.ts
Normal file
15
app/react/docker/proxy/queries/nodes/build-url.ts
Normal file
|
@ -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
|
||||
);
|
||||
}
|
8
app/react/docker/proxy/queries/nodes/query-keys.ts
Normal file
8
app/react/docker/proxy/queries/nodes/query-keys.ts
Normal file
|
@ -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,
|
||||
};
|
21
app/react/docker/proxy/queries/nodes/useNodes.ts
Normal file
21
app/react/docker/proxy/queries/nodes/useNodes.ts
Normal file
|
@ -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<Array<Node>>(buildUrl(environmentId));
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw parseAxiosError(error, 'Unable to retrieve nodes');
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<NestedDatatable
|
||||
columns={columns}
|
||||
dataset={dataset}
|
||||
search={search}
|
||||
emptyContentLabel="No task matching filter."
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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<DecoratedTask, unknown>) {
|
||||
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 ? (
|
||||
<ContainerQuickActions
|
||||
containerId={item.Container.Id}
|
||||
nodeName={item.Container.NodeName}
|
||||
status={item.Container.Status}
|
||||
state={state}
|
||||
/>
|
||||
) : (
|
||||
<TaskTableQuickActions taskId={item.Id} />
|
||||
);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
|
||||
import { DecoratedTask } from '../types';
|
||||
|
||||
export const columnHelper = createColumnHelper<DecoratedTask>();
|
|
@ -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()),
|
||||
}),
|
||||
];
|
|
@ -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<DecoratedTask, string>) {
|
||||
const environmentId = useEnvironmentId();
|
||||
|
||||
const nodesQuery = useNodes(environmentId);
|
||||
|
||||
const nodes = nodesQuery.data || [];
|
||||
return getNodeName(getValue(), nodes);
|
||||
}
|
||||
|
||||
function getNodeName(nodeId: string, nodes: Array<Node>) {
|
||||
const node = nodes.find((node) => node.ID === nodeId);
|
||||
if (node?.Description?.Hostname) {
|
||||
return node.Description.Hostname;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
|
@ -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 (
|
||||
<span className={clsx('label', `label-${taskStatusBadge(value)}`)}>
|
||||
{value}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -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<DecoratedTask, string>) {
|
||||
const environmentQuery = useCurrentEnvironment();
|
||||
|
||||
if (!environmentQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = getValue();
|
||||
const isAgent = isAgentEnvironment(environmentQuery.data.Type);
|
||||
|
||||
return isAgent && item.Container ? (
|
||||
<Link
|
||||
to="docker.containers.container"
|
||||
params={{ id: item.Container.Id, nodeName: item.Container.NodeName }}
|
||||
className="monospaced"
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
to="docker.tasks.task"
|
||||
params={{ id: item.Id }}
|
||||
className="monospaced"
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { TasksDatatable } from './TasksDatatable';
|
|
@ -0,0 +1,6 @@
|
|||
import { TaskViewModel } from '@/docker/models/task';
|
||||
import { DockerContainer } from '@/react/docker/containers/types';
|
||||
|
||||
export type DecoratedTask = TaskViewModel & {
|
||||
Container?: DockerContainer;
|
||||
};
|
46
app/react/docker/services/common/TaskTableQuickActions.tsx
Normal file
46
app/react/docker/services/common/TaskTableQuickActions.tsx
Normal file
|
@ -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 (
|
||||
<div className="inline-flex space-x-1">
|
||||
{state.showQuickActionLogs && (
|
||||
<Authorized authorizations="DockerTaskLogs">
|
||||
<Link
|
||||
to="docker.tasks.task.logs"
|
||||
params={{ id: taskId }}
|
||||
title="Logs"
|
||||
>
|
||||
<Icon icon={FileText} className="space-right" />
|
||||
</Link>
|
||||
</Authorized>
|
||||
)}
|
||||
|
||||
{state.showQuickActionInspect && (
|
||||
<Authorized authorizations="DockerTaskInspect">
|
||||
<Link to="docker.tasks.task" params={{ id: taskId }} title="Inspect">
|
||||
<Icon icon={Info} className="space-right" />
|
||||
</Link>
|
||||
</Authorized>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue