1
0
Fork 0
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:
Chaim Lev-Ari 2024-08-28 13:41:15 -06:00 committed by GitHub
parent 9797201c2a
commit 33ce841040
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 71 additions and 69 deletions

View file

@ -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"

View 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];
}

View 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;
}

View file

@ -0,0 +1,5 @@
export type DockerEvent = {
Time: number;
Type: string;
Details: string;
};

View file

@ -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,
};

View file

@ -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[]>(