mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 07:49:41 +02:00
refactor(docker/events): migrate list view to react [EE-2228] (#11581)
This commit is contained in:
parent
9797201c2a
commit
33ce841040
11 changed files with 71 additions and 69 deletions
|
@ -1,5 +1,6 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { Clock } from 'lucide-react';
|
||||
import { EventMessage } from 'docker-types/generated/1.41';
|
||||
|
||||
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
|
||||
|
||||
|
@ -7,26 +8,22 @@ import { Datatable } from '@@/datatables';
|
|||
import { createPersistedStore } from '@@/datatables/types';
|
||||
import { useTableState } from '@@/datatables/useTableState';
|
||||
|
||||
type DockerEvent = {
|
||||
Time: number;
|
||||
Type: string;
|
||||
Details: string;
|
||||
};
|
||||
import { createEventDetails } from './model';
|
||||
|
||||
const columnHelper = createColumnHelper<DockerEvent>();
|
||||
const columnHelper = createColumnHelper<EventMessage>();
|
||||
|
||||
export const columns = [
|
||||
columnHelper.accessor('Time', {
|
||||
columnHelper.accessor('time', {
|
||||
header: 'Date',
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
return isoDateFromTimestamp(value);
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('Type', {
|
||||
columnHelper.accessor((c) => c.Type, {
|
||||
header: 'Type',
|
||||
}),
|
||||
columnHelper.accessor('Details', {
|
||||
columnHelper.accessor((c) => createEventDetails(c), {
|
||||
header: 'Details',
|
||||
}),
|
||||
];
|
||||
|
@ -37,12 +34,17 @@ const settingsStore = createPersistedStore(tableKey, {
|
|||
desc: true,
|
||||
});
|
||||
|
||||
export function EventsDatatable({ dataset }: { dataset: Array<DockerEvent> }) {
|
||||
export function EventsDatatable({
|
||||
dataset,
|
||||
}: {
|
||||
dataset?: Array<EventMessage>;
|
||||
}) {
|
||||
const tableState = useTableState(settingsStore, tableKey);
|
||||
|
||||
return (
|
||||
<Datatable
|
||||
dataset={dataset ?? []}
|
||||
isLoading={!dataset}
|
||||
columns={columns}
|
||||
settingsManager={tableState}
|
||||
title="Events"
|
||||
|
|
33
app/react/docker/events/ListView.tsx
Normal file
33
app/react/docker/events/ListView.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { useState } from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
|
||||
import { useEvents } from '../proxy/queries/useEvents';
|
||||
|
||||
import { EventsDatatable } from './EventsDatatables';
|
||||
|
||||
export function ListView() {
|
||||
const { since, until } = useDateRange();
|
||||
const envId = useEnvironmentId();
|
||||
const eventsQuery = useEvents(envId, { params: { since, until } });
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Event list" breadcrumbs="Events" reload />
|
||||
|
||||
<EventsDatatable dataset={eventsQuery.data} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function useDateRange() {
|
||||
return useState(() => {
|
||||
const since = moment().subtract(24, 'hour').unix();
|
||||
const until = moment().unix();
|
||||
|
||||
return { since, until };
|
||||
})[0];
|
||||
}
|
120
app/react/docker/events/model.ts
Normal file
120
app/react/docker/events/model.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { EventMessage } from 'docker-types/generated/1.41';
|
||||
|
||||
type EventType = NonNullable<EventMessage['Type']>;
|
||||
type Action = string;
|
||||
|
||||
type Attributes = {
|
||||
id: string;
|
||||
name: string;
|
||||
exitCode: string;
|
||||
};
|
||||
|
||||
type EventToTemplateMap = Record<EventType, ActionToTemplateMap>;
|
||||
type ActionToTemplateMap = Record<Action, TemplateBuilder>;
|
||||
type TemplateBuilder = (attr: Attributes) => string;
|
||||
|
||||
/**
|
||||
* {
|
||||
* [EventType]: {
|
||||
* [Action]: TemplateBuilder,
|
||||
* [Action]: TemplateBuilder
|
||||
* },
|
||||
* [EventType]: {
|
||||
* [Action]: TemplateBuilder,
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* EventType are known and defined by Docker specs
|
||||
* Action are unknown and specific for each EventType
|
||||
*/
|
||||
const templates: EventToTemplateMap = {
|
||||
builder: {},
|
||||
config: {},
|
||||
container: {
|
||||
stop: ({ name }) => `Container ${name} stopped`,
|
||||
destroy: ({ name }) => `Container ${name} deleted`,
|
||||
create: ({ name }) => `Container ${name} created`,
|
||||
start: ({ name }) => `Container ${name} started`,
|
||||
kill: ({ name }) => `Container ${name} killed`,
|
||||
die: ({ name, exitCode }) =>
|
||||
`Container ${name} exited with status code ${exitCode}`,
|
||||
commit: ({ name }) => `Container ${name} committed`,
|
||||
restart: ({ name }) => `Container ${name} restarted`,
|
||||
pause: ({ name }) => `Container ${name} paused`,
|
||||
unpause: ({ name }) => `Container ${name} unpaused`,
|
||||
attach: ({ name }) => `Container ${name} attached`,
|
||||
detach: ({ name }) => `Container ${name} detached`,
|
||||
copy: ({ name }) => `Container ${name} copied`,
|
||||
export: ({ name }) => `Container ${name} exported`,
|
||||
health_status: ({ name }) => `Container ${name} executed health status`,
|
||||
oom: ({ name }) => `Container ${name} goes in out of memory`,
|
||||
rename: ({ name }) => `Container ${name} renamed`,
|
||||
resize: ({ name }) => `Container ${name} resized`,
|
||||
top: ({ name }) => `Showed running processes for container ${name}`,
|
||||
update: ({ name }) => `Container ${name} updated`,
|
||||
exec_create: () => `Exec instance created`,
|
||||
exec_start: () => `Exec instance started`,
|
||||
exec_die: () => `Exec instance exited`,
|
||||
},
|
||||
daemon: {},
|
||||
image: {
|
||||
delete: () => `Image deleted`,
|
||||
import: ({ id }) => `Image ${id} imported`,
|
||||
load: ({ id }) => `Image ${id} loaded`,
|
||||
tag: ({ name }) => `New tag created for ${name}`,
|
||||
untag: () => `Image untagged`,
|
||||
save: ({ id }) => `Image ${id} saved`,
|
||||
pull: ({ id }) => `Image ${id} pulled`,
|
||||
push: ({ id }) => `Image ${id} pushed`,
|
||||
},
|
||||
network: {
|
||||
create: ({ name }) => `Network ${name} created`,
|
||||
destroy: ({ name }) => `Network ${name} deleted`,
|
||||
remove: ({ name }) => `Network ${name} removed`,
|
||||
connect: ({ name }) => `Container connected to ${name} network`,
|
||||
disconnect: ({ name }) => `Container disconnected from ${name} network`,
|
||||
prune: () => `Networks pruned`,
|
||||
},
|
||||
node: {},
|
||||
plugin: {},
|
||||
secret: {},
|
||||
service: {},
|
||||
volume: {
|
||||
create: ({ id }) => `Volume ${id} created`,
|
||||
destroy: ({ id }) => `Volume ${id} deleted`,
|
||||
mount: ({ id }) => `Volume ${id} mounted`,
|
||||
unmount: ({ id }) => `Volume ${id} unmounted`,
|
||||
},
|
||||
};
|
||||
|
||||
export function createEventDetails(event: EventMessage) {
|
||||
const eventType = event.Type ?? '';
|
||||
|
||||
// An action can be `action:extra`
|
||||
// For example `docker exec -it CONTAINER sh`
|
||||
// Generates the action `exec_create: sh`
|
||||
let extra = '';
|
||||
let action = event.Action ?? '';
|
||||
const hasColon = action?.indexOf(':') ?? -1;
|
||||
if (hasColon !== -1) {
|
||||
extra = action?.substring(hasColon) ?? '';
|
||||
action = action?.substring(0, hasColon);
|
||||
}
|
||||
|
||||
const attr: Attributes = {
|
||||
id: event.Actor?.ID || '',
|
||||
name: event.Actor?.Attributes?.name || '',
|
||||
exitCode: event.Actor?.Attributes?.exitCode || '',
|
||||
};
|
||||
|
||||
// Event types are defined by the docker API specs
|
||||
// Each event has it own set of actions, which a unknown/not defined by specs
|
||||
// If the received event or action has no builder associated to it
|
||||
// We consider the event unsupported and we provide the raw data
|
||||
const detailsBuilder = templates[eventType as EventType]?.[action];
|
||||
const details = detailsBuilder
|
||||
? detailsBuilder(attr)
|
||||
: `Unsupported event: ${eventType} / ${action}`;
|
||||
|
||||
return details + extra;
|
||||
}
|
5
app/react/docker/events/types.ts
Normal file
5
app/react/docker/events/types.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export type DockerEvent = {
|
||||
Time: number;
|
||||
Type: string;
|
||||
Details: string;
|
||||
};
|
|
@ -3,4 +3,6 @@ import { EnvironmentId } from '@/react/portainer/environments/types';
|
|||
export const queryKeys = {
|
||||
base: (environmentId: EnvironmentId) =>
|
||||
[environmentId, 'docker', 'proxy'] as const,
|
||||
events: (environmentId: EnvironmentId, params?: object) =>
|
||||
[...queryKeys.base(environmentId), 'events', params] as const,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { EventMessage } from 'docker-types/generated/1.41';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import axios, {
|
||||
jsonObjectsToArrayHandler,
|
||||
|
@ -7,16 +8,26 @@ import axios, {
|
|||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { buildDockerProxyUrl } from './buildDockerProxyUrl';
|
||||
import { queryKeys } from './query-keys';
|
||||
|
||||
type Params = { since?: number; until?: number };
|
||||
|
||||
export function useEvents(
|
||||
environmentId: EnvironmentId,
|
||||
{ params }: { params?: Params } = {}
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: [...queryKeys.events(environmentId, params)],
|
||||
queryFn: () => getEvents(environmentId, params),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw docker API proxy
|
||||
* @param environmentId
|
||||
* @param param1
|
||||
* @returns
|
||||
*/
|
||||
export async function getEvents(
|
||||
environmentId: EnvironmentId,
|
||||
{ since, until }: { since: string; until: string }
|
||||
{ since, until }: Params = {}
|
||||
) {
|
||||
try {
|
||||
const { data } = await axios.get<EventMessage[]>(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue