mirror of
https://github.com/portainer/portainer.git
synced 2025-08-06 06:15:22 +02:00
refactor(nomad): sync frontend with EE [EE-3353] (#7758)
This commit is contained in:
parent
78dcba614d
commit
881e99df53
68 changed files with 1799 additions and 17 deletions
|
@ -0,0 +1,163 @@
|
|||
import { Fragment, useEffect } from 'react';
|
||||
import {
|
||||
useFilters,
|
||||
useGlobalFilter,
|
||||
usePagination,
|
||||
useSortBy,
|
||||
useTable,
|
||||
} from 'react-table';
|
||||
|
||||
import { NomadEvent } from '@/react/nomad/types';
|
||||
import { useDebounce } from '@/react/hooks/useDebounce';
|
||||
|
||||
import { PaginationControls } from '@@/PaginationControls';
|
||||
import {
|
||||
Table,
|
||||
TableContainer,
|
||||
TableHeaderRow,
|
||||
TableRow,
|
||||
TableTitle,
|
||||
} from '@@/datatables';
|
||||
import { multiple } from '@@/datatables/filter-types';
|
||||
import { useTableSettings } from '@@/datatables/useTableSettings';
|
||||
import { SearchBar, useSearchBarState } from '@@/datatables/SearchBar';
|
||||
import { TableFooter } from '@@/datatables/TableFooter';
|
||||
import { TableContent } from '@@/datatables/TableContent';
|
||||
|
||||
import { useColumns } from './columns';
|
||||
|
||||
export interface EventsDatatableProps {
|
||||
data: NomadEvent[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export interface EventsTableSettings {
|
||||
autoRefreshRate: number;
|
||||
pageSize: number;
|
||||
sortBy: { id: string; desc: boolean };
|
||||
}
|
||||
|
||||
export function EventsDatatable({ data, isLoading }: EventsDatatableProps) {
|
||||
const { settings, setTableSettings } =
|
||||
useTableSettings<EventsTableSettings>();
|
||||
const [searchBarValue, setSearchBarValue] = useSearchBarState('events');
|
||||
const columns = useColumns();
|
||||
const debouncedSearchValue = useDebounce(searchBarValue);
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
page,
|
||||
prepareRow,
|
||||
gotoPage,
|
||||
setPageSize,
|
||||
setGlobalFilter,
|
||||
state: { pageIndex, pageSize },
|
||||
} = useTable<NomadEvent>(
|
||||
{
|
||||
defaultCanFilter: false,
|
||||
columns,
|
||||
data,
|
||||
filterTypes: { multiple },
|
||||
initialState: {
|
||||
pageSize: settings.pageSize || 10,
|
||||
sortBy: [settings.sortBy],
|
||||
globalFilter: searchBarValue,
|
||||
},
|
||||
},
|
||||
useFilters,
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
usePagination
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setGlobalFilter(debouncedSearchValue);
|
||||
}, [debouncedSearchValue, setGlobalFilter]);
|
||||
|
||||
const tableProps = getTableProps();
|
||||
const tbodyProps = getTableBodyProps();
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableTitle icon="fa-history" label="Events" />
|
||||
|
||||
<SearchBar value={searchBarValue} onChange={handleSearchBarChange} />
|
||||
|
||||
<Table
|
||||
className={tableProps.className}
|
||||
role={tableProps.role}
|
||||
style={tableProps.style}
|
||||
>
|
||||
<thead>
|
||||
{headerGroups.map((headerGroup) => {
|
||||
const { key, className, role, style } =
|
||||
headerGroup.getHeaderGroupProps();
|
||||
|
||||
return (
|
||||
<TableHeaderRow<NomadEvent>
|
||||
key={key}
|
||||
className={className}
|
||||
role={role}
|
||||
style={style}
|
||||
headers={headerGroup.headers}
|
||||
onSortChange={handleSortChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</thead>
|
||||
<tbody
|
||||
className={tbodyProps.className}
|
||||
role={tbodyProps.role}
|
||||
style={tbodyProps.style}
|
||||
>
|
||||
<TableContent
|
||||
rows={page}
|
||||
prepareRow={prepareRow}
|
||||
isLoading={isLoading}
|
||||
emptyContent="No events found"
|
||||
renderRow={(row, { key, className, role, style }) => (
|
||||
<Fragment key={key}>
|
||||
<TableRow<NomadEvent>
|
||||
cells={row.cells}
|
||||
key={key}
|
||||
className={className}
|
||||
role={role}
|
||||
style={style}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
/>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<TableFooter>
|
||||
<PaginationControls
|
||||
showAll
|
||||
pageLimit={pageSize}
|
||||
page={pageIndex + 1}
|
||||
onPageChange={(p) => gotoPage(p - 1)}
|
||||
totalCount={data.length}
|
||||
onPageLimitChange={handlePageSizeChange}
|
||||
/>
|
||||
</TableFooter>
|
||||
</TableContainer>
|
||||
);
|
||||
|
||||
function handlePageSizeChange(pageSize: number) {
|
||||
setPageSize(pageSize);
|
||||
setTableSettings((settings) => ({ ...settings, pageSize }));
|
||||
}
|
||||
|
||||
function handleSearchBarChange(value: string) {
|
||||
setSearchBarValue(value);
|
||||
}
|
||||
|
||||
function handleSortChange(id: string, desc: boolean) {
|
||||
setTableSettings((settings) => ({
|
||||
...settings,
|
||||
sortBy: { id, desc },
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { Column } from 'react-table';
|
||||
|
||||
import { NomadEvent } from '@/react/nomad/types';
|
||||
import { isoDate } from '@/portainer/filters/filters';
|
||||
|
||||
export const date: Column<NomadEvent> = {
|
||||
Header: 'Date',
|
||||
accessor: (row) => (row.Date ? isoDate(row.Date) : '-'),
|
||||
id: 'date',
|
||||
disableFilters: true,
|
||||
canHide: true,
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { date } from './date';
|
||||
import { type } from './type';
|
||||
import { message } from './message';
|
||||
|
||||
export function useColumns() {
|
||||
return useMemo(() => [date, type, message], []);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { Column } from 'react-table';
|
||||
|
||||
import { NomadEvent } from '@/react/nomad/types';
|
||||
|
||||
export const message: Column<NomadEvent> = {
|
||||
Header: 'Message',
|
||||
accessor: 'Message',
|
||||
id: 'message',
|
||||
disableFilters: true,
|
||||
canHide: true,
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
import { Column } from 'react-table';
|
||||
|
||||
import { NomadEvent } from '@/react/nomad/types';
|
||||
|
||||
export const type: Column<NomadEvent> = {
|
||||
Header: 'Type',
|
||||
accessor: 'Type',
|
||||
id: 'type',
|
||||
disableFilters: true,
|
||||
canHide: true,
|
||||
};
|
1
app/react/nomad/jobs/EventsView/EventsDatatable/index.ts
Normal file
1
app/react/nomad/jobs/EventsView/EventsDatatable/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { EventsDatatable } from './EventsDatatable';
|
62
app/react/nomad/jobs/EventsView/EventsView.tsx
Normal file
62
app/react/nomad/jobs/EventsView/EventsView.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { NomadEventsList } from '@/react/nomad/types';
|
||||
|
||||
import { TableSettingsProvider } from '@@/datatables/useTableSettings';
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
|
||||
import { EventsDatatable } from './EventsDatatable';
|
||||
import { useEvents } from './useEvents';
|
||||
|
||||
export function EventsView() {
|
||||
const environmentId = useEnvironmentId();
|
||||
const { query, invalidateQuery } = useEvents();
|
||||
const {
|
||||
params: { jobID, taskName },
|
||||
} = useCurrentStateAndParams();
|
||||
|
||||
const breadcrumbs = [
|
||||
{
|
||||
label: 'Nomad Jobs',
|
||||
link: 'nomad.jobs',
|
||||
linkParams: { id: environmentId },
|
||||
},
|
||||
{ label: jobID },
|
||||
{ label: taskName },
|
||||
{ label: 'Events' },
|
||||
];
|
||||
|
||||
const defaultSettings = {
|
||||
pageSize: 10,
|
||||
sortBy: {},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* header */}
|
||||
<PageHeader
|
||||
title="Event list"
|
||||
breadcrumbs={breadcrumbs}
|
||||
reload
|
||||
loading={query.isLoading || query.isFetching}
|
||||
onReload={invalidateQuery}
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<TableSettingsProvider
|
||||
defaults={defaultSettings}
|
||||
storageKey="nomad-events"
|
||||
>
|
||||
{/* events table */}
|
||||
<EventsDatatable
|
||||
data={(query.data || []) as NomadEventsList}
|
||||
isLoading={query.isLoading}
|
||||
/>
|
||||
</TableSettingsProvider>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
1
app/react/nomad/jobs/EventsView/index.ts
Normal file
1
app/react/nomad/jobs/EventsView/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { EventsView } from './EventsView';
|
75
app/react/nomad/jobs/EventsView/useEvents.ts
Normal file
75
app/react/nomad/jobs/EventsView/useEvents.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { useQuery, useQueryClient } from 'react-query';
|
||||
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||
|
||||
import * as notifications from '@/portainer/services/notifications';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
|
||||
import { NomadEventsList } from '../../types';
|
||||
|
||||
export function useEvents() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
params: {
|
||||
endpointId: environmentID,
|
||||
allocationID,
|
||||
jobID,
|
||||
taskName,
|
||||
namespace,
|
||||
},
|
||||
} = useCurrentStateAndParams();
|
||||
|
||||
if (!environmentID) {
|
||||
throw new Error('endpointId url param is required');
|
||||
}
|
||||
|
||||
const key = [
|
||||
'environments',
|
||||
environmentID,
|
||||
'nomad',
|
||||
'events',
|
||||
allocationID,
|
||||
jobID,
|
||||
taskName,
|
||||
namespace,
|
||||
];
|
||||
|
||||
function invalidateQuery() {
|
||||
return queryClient.invalidateQueries(key);
|
||||
}
|
||||
|
||||
const query = useQuery(
|
||||
key,
|
||||
() =>
|
||||
getTaskEvents(environmentID, allocationID, jobID, taskName, namespace),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
onError: (err) => {
|
||||
notifications.error('Failed loading events', err as Error, '');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return { query, invalidateQuery };
|
||||
}
|
||||
|
||||
export async function getTaskEvents(
|
||||
environmentId: EnvironmentId,
|
||||
allocationId: string,
|
||||
jobId: string,
|
||||
taskName: string,
|
||||
namespace: string
|
||||
) {
|
||||
try {
|
||||
const ret = await axios.get<NomadEventsList>(
|
||||
`/nomad/endpoints/${environmentId}/allocation/${allocationId}/events`,
|
||||
{
|
||||
params: { jobId, taskName, namespace },
|
||||
}
|
||||
);
|
||||
return ret.data;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue