feat: instance pv events
This commit is contained in:
@@ -76,6 +76,8 @@ export default {
|
|||||||
'gpuservice.instance.event.lastSeen': 'Last Seen',
|
'gpuservice.instance.event.lastSeen': 'Last Seen',
|
||||||
'gpuservice.instance.event.recentHourTip':
|
'gpuservice.instance.event.recentHourTip':
|
||||||
'Only events from the last hour are shown',
|
'Only events from the last hour are shown',
|
||||||
|
'gpuservice.instance.event.tab.instance': 'Instance Events',
|
||||||
|
'gpuservice.instance.event.tab.volume': 'Volume Events',
|
||||||
'gpuservice.instance.recreate.confirm.title': 'Confirm recreation',
|
'gpuservice.instance.recreate.confirm.title': 'Confirm recreation',
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
'gpuservice.instance.recreate.confirm.content':
|
||||||
'The current instance will be deleted first, then recreated with the current configuration.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
'The current instance will be deleted first, then recreated with the current configuration.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ export default {
|
|||||||
'gpuservice.instance.event.lastSeen': '最終発生',
|
'gpuservice.instance.event.lastSeen': '最終発生',
|
||||||
'gpuservice.instance.event.recentHourTip':
|
'gpuservice.instance.event.recentHourTip':
|
||||||
'直近 1 時間のイベントのみ表示されます',
|
'直近 1 時間のイベントのみ表示されます',
|
||||||
|
'gpuservice.instance.event.tab.instance': 'インスタンスイベント',
|
||||||
|
'gpuservice.instance.event.tab.volume': 'ボリュームイベント',
|
||||||
'gpuservice.instance.recreate.confirm.title': '再作成を確認しますか',
|
'gpuservice.instance.recreate.confirm.title': '再作成を確認しますか',
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
'gpuservice.instance.recreate.confirm.content':
|
||||||
'現在のインスタンスを削除した後、現在の構成で再作成します。\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
'現在のインスタンスを削除した後、現在の構成で再作成します。\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ export default {
|
|||||||
'gpuservice.instance.event.lastSeen': 'Последнее событие',
|
'gpuservice.instance.event.lastSeen': 'Последнее событие',
|
||||||
'gpuservice.instance.event.recentHourTip':
|
'gpuservice.instance.event.recentHourTip':
|
||||||
'Отображаются только события за последний час',
|
'Отображаются только события за последний час',
|
||||||
|
'gpuservice.instance.event.tab.instance': 'События экземпляра',
|
||||||
|
'gpuservice.instance.event.tab.volume': 'События тома',
|
||||||
'gpuservice.instance.recreate.confirm.title': 'Подтвердить пересоздание',
|
'gpuservice.instance.recreate.confirm.title': 'Подтвердить пересоздание',
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
'gpuservice.instance.recreate.confirm.content':
|
||||||
'Текущий экземпляр будет сначала удалён, а затем пересоздан с текущей конфигурацией.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
'Текущий экземпляр будет сначала удалён, а затем пересоздан с текущей конфигурацией.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ export default {
|
|||||||
'gpuservice.instance.event.lastSeen': 'Son Görülen',
|
'gpuservice.instance.event.lastSeen': 'Son Görülen',
|
||||||
'gpuservice.instance.event.recentHourTip':
|
'gpuservice.instance.event.recentHourTip':
|
||||||
'Yalnızca son bir saatteki olaylar gösterilir',
|
'Yalnızca son bir saatteki olaylar gösterilir',
|
||||||
|
'gpuservice.instance.event.tab.instance': 'Örnek Olayları',
|
||||||
|
'gpuservice.instance.event.tab.volume': 'Birim Olayları',
|
||||||
'gpuservice.instance.recreate.confirm.title':
|
'gpuservice.instance.recreate.confirm.title':
|
||||||
'Yeniden oluşturma onaylansın mı',
|
'Yeniden oluşturma onaylansın mı',
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
'gpuservice.instance.recreate.confirm.content':
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ export default {
|
|||||||
'gpuservice.instance.event.count': '次数',
|
'gpuservice.instance.event.count': '次数',
|
||||||
'gpuservice.instance.event.lastSeen': '最近发生',
|
'gpuservice.instance.event.lastSeen': '最近发生',
|
||||||
'gpuservice.instance.event.recentHourTip': '仅显示最近一小时的事件。',
|
'gpuservice.instance.event.recentHourTip': '仅显示最近一小时的事件。',
|
||||||
|
'gpuservice.instance.event.tab.instance': '实例事件',
|
||||||
|
'gpuservice.instance.event.tab.volume': '存储卷事件',
|
||||||
'gpuservice.instance.recreate.confirm.title': '确认重新创建',
|
'gpuservice.instance.recreate.confirm.title': '确认重新创建',
|
||||||
'gpuservice.instance.recreate.confirm.content':
|
'gpuservice.instance.recreate.confirm.content':
|
||||||
'系统将先删除当前实例,然后使用当前配置重新创建。\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
'系统将先删除当前实例,然后使用当前配置重新创建。\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
||||||
|
|||||||
@@ -187,6 +187,31 @@ export async function queryGPUServiceInstanceEvents(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function queryGPUServiceInstancePVEvents(
|
||||||
|
params: {
|
||||||
|
namespace: string;
|
||||||
|
name: string;
|
||||||
|
clusterID?: number;
|
||||||
|
},
|
||||||
|
options?: any
|
||||||
|
) {
|
||||||
|
if (!params.clusterID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return request<InstanceEvents>(
|
||||||
|
`${GPU_SERVICE_INSTANCE_PV_EVENTS_API({
|
||||||
|
namespace: params.namespace,
|
||||||
|
clusterID: params.clusterID,
|
||||||
|
name: params.name
|
||||||
|
})}`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
params: omitPathParams(_.omit(params, ['name'])),
|
||||||
|
cancelToken: options?.token
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function queryGPUServiceInstanceLog(
|
export async function queryGPUServiceInstanceLog(
|
||||||
params: InstanceLogQueryParams & {
|
params: InstanceLogQueryParams & {
|
||||||
namespace: string;
|
namespace: string;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { createAxiosToken } from '@/hooks/use-chunk-request';
|
|
||||||
import { ReloadOutlined } from '@ant-design/icons';
|
import { ReloadOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
AlertBlockInfo,
|
AlertBlockInfo,
|
||||||
@@ -7,12 +6,13 @@ import {
|
|||||||
StatusTag
|
StatusTag
|
||||||
} from '@gpustack/core-ui';
|
} from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Table } from 'antd';
|
import { Button, Table, Tabs, TabsProps } from 'antd';
|
||||||
import type { ColumnsType } from 'antd/lib/table';
|
import type { ColumnsType } from 'antd/lib/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { queryGPUServiceInstanceEvents } from '../apis';
|
|
||||||
import { InstanceEventItem } from '../config/types';
|
import { InstanceEventItem } from '../config/types';
|
||||||
|
import useQueryInstanceEvents from '../services/use-query-instance-events';
|
||||||
|
import useQueryVolumeEvents from '../services/use-query-volume-events';
|
||||||
|
|
||||||
type ViewEventsModalProps = {
|
type ViewEventsModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -31,119 +31,170 @@ const eventTypeStatus: Record<string, 'success' | 'warning' | 'error'> = {
|
|||||||
|
|
||||||
const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { open, onCancel, name, namespace, clusterID } = props || {};
|
const { open, onCancel, name } = props || {};
|
||||||
const [events, setEvents] = useState<InstanceEventItem[]>([]);
|
const [activeKey, setActiveKey] = useState('instance');
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const tokenRef = useRef<ReturnType<typeof createAxiosToken> | null>(null);
|
|
||||||
|
|
||||||
const fetchEvents = useCallback(async () => {
|
const {
|
||||||
if (!name || !clusterID || !namespace) return;
|
detailData: instanceEventsData,
|
||||||
tokenRef.current?.cancel?.();
|
loading: instanceLoading,
|
||||||
const source = createAxiosToken();
|
cancelRequest: cancelInstanceRequest,
|
||||||
tokenRef.current = source;
|
fetchData: fetchInstanceEvents
|
||||||
setLoading(true);
|
} = useQueryInstanceEvents();
|
||||||
try {
|
|
||||||
const res = await queryGPUServiceInstanceEvents(
|
const {
|
||||||
{ name, namespace, clusterID },
|
detailData: volumeEventsData,
|
||||||
{ token: source.token }
|
loading: volumeLoading,
|
||||||
);
|
cancelRequest: cancelVolumeRequest,
|
||||||
setEvents(res?.items ?? []);
|
fetchData: fetchVolumeEvents
|
||||||
} catch (e) {
|
} = useQueryVolumeEvents();
|
||||||
// request cancellation or network failure — keep prior items
|
|
||||||
} finally {
|
const instanceEvents = instanceEventsData?.items ?? [];
|
||||||
setLoading(false);
|
const volumeEvents = volumeEventsData?.items ?? [];
|
||||||
}
|
|
||||||
}, [name, namespace, clusterID]);
|
const refreshAll = () => {
|
||||||
|
if (!name) return;
|
||||||
|
fetchInstanceEvents({ name });
|
||||||
|
fetchVolumeEvents({ name });
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
fetchEvents();
|
refreshAll();
|
||||||
} else {
|
} else {
|
||||||
tokenRef.current?.cancel?.();
|
cancelInstanceRequest();
|
||||||
setEvents([]);
|
cancelVolumeRequest();
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
tokenRef.current?.cancel?.();
|
cancelInstanceRequest();
|
||||||
|
cancelVolumeRequest();
|
||||||
};
|
};
|
||||||
}, [open, fetchEvents]);
|
}, [open]);
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = () => {
|
||||||
tokenRef.current?.cancel?.();
|
cancelInstanceRequest();
|
||||||
|
cancelVolumeRequest();
|
||||||
onCancel();
|
onCancel();
|
||||||
}, [onCancel]);
|
};
|
||||||
|
|
||||||
const columns: ColumnsType<InstanceEventItem> = [
|
const columns: ColumnsType<InstanceEventItem> = useMemo(
|
||||||
{
|
() => [
|
||||||
title: intl.formatMessage({ id: 'common.table.type' }),
|
{
|
||||||
dataIndex: 'type',
|
title: intl.formatMessage({ id: 'common.table.type' }),
|
||||||
key: 'type',
|
dataIndex: 'type',
|
||||||
width: 110,
|
key: 'type',
|
||||||
render: (value: string) => (
|
width: 110,
|
||||||
<StatusTag
|
render: (value: string) => (
|
||||||
statusValue={{
|
<StatusTag
|
||||||
status: eventTypeStatus[value] ?? 'inactive',
|
statusValue={{
|
||||||
text: value || '-'
|
status: eventTypeStatus[value] ?? 'inactive',
|
||||||
}}
|
text: value || '-'
|
||||||
/>
|
}}
|
||||||
)
|
/>
|
||||||
},
|
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: 'gpuservice.instance.event.reason' }),
|
|
||||||
dataIndex: 'reason',
|
|
||||||
key: 'reason',
|
|
||||||
width: 180,
|
|
||||||
ellipsis: { showTitle: false },
|
|
||||||
render: (text: string) => (
|
|
||||||
<AutoTooltip ghost style={{ maxWidth: 180 }}>
|
|
||||||
{text || '-'}
|
|
||||||
</AutoTooltip>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: 'gpuservice.instance.event.message' }),
|
|
||||||
dataIndex: 'message',
|
|
||||||
key: 'message',
|
|
||||||
ellipsis: { showTitle: false },
|
|
||||||
render: (text: string) => <AutoTooltip ghost>{text || '-'}</AutoTooltip>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: 'gpuservice.instance.event.source' }),
|
|
||||||
key: 'source',
|
|
||||||
width: 200,
|
|
||||||
ellipsis: { showTitle: false },
|
|
||||||
render: (_text, record) => {
|
|
||||||
const src =
|
|
||||||
record.source?.component ||
|
|
||||||
record.reportingComponent ||
|
|
||||||
record.reportingInstance ||
|
|
||||||
'-';
|
|
||||||
return (
|
|
||||||
<AutoTooltip ghost style={{ maxWidth: 200 }}>
|
|
||||||
{src}
|
|
||||||
</AutoTooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: 'gpuservice.instance.event.count' }),
|
|
||||||
dataIndex: 'count',
|
|
||||||
key: 'count',
|
|
||||||
width: 80,
|
|
||||||
render: (value?: number) => value ?? '-'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: 'gpuservice.instance.event.lastSeen' }),
|
|
||||||
key: 'lastTimestamp',
|
|
||||||
width: 180,
|
|
||||||
render: (_text, record) =>
|
|
||||||
formatRelative(
|
|
||||||
record.lastTimestamp ||
|
|
||||||
record.series?.lastObservedTime ||
|
|
||||||
record.eventTime
|
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'gpuservice.instance.event.reason' }),
|
||||||
|
dataIndex: 'reason',
|
||||||
|
key: 'reason',
|
||||||
|
width: 180,
|
||||||
|
ellipsis: { showTitle: false },
|
||||||
|
render: (text: string) => (
|
||||||
|
<AutoTooltip ghost style={{ maxWidth: 180 }}>
|
||||||
|
{text || '-'}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'gpuservice.instance.event.message' }),
|
||||||
|
dataIndex: 'message',
|
||||||
|
key: 'message',
|
||||||
|
ellipsis: { showTitle: false },
|
||||||
|
render: (text: string) => <AutoTooltip ghost>{text || '-'}</AutoTooltip>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'gpuservice.instance.event.source' }),
|
||||||
|
key: 'source',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: { showTitle: false },
|
||||||
|
render: (_text, record) => {
|
||||||
|
const src =
|
||||||
|
record.source?.component ||
|
||||||
|
record.reportingComponent ||
|
||||||
|
record.reportingInstance ||
|
||||||
|
'-';
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost style={{ maxWidth: 200 }}>
|
||||||
|
{src}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'gpuservice.instance.event.count' }),
|
||||||
|
dataIndex: 'count',
|
||||||
|
key: 'count',
|
||||||
|
width: 80,
|
||||||
|
render: (value?: number) => value ?? '-'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.event.lastSeen'
|
||||||
|
}),
|
||||||
|
key: 'lastTimestamp',
|
||||||
|
width: 180,
|
||||||
|
render: (_text, record) =>
|
||||||
|
formatRelative(
|
||||||
|
record.lastTimestamp ||
|
||||||
|
record.series?.lastObservedTime ||
|
||||||
|
record.eventTime
|
||||||
|
)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[intl]
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderTable = (
|
||||||
|
dataSource: InstanceEventItem[],
|
||||||
|
tableLoading: boolean
|
||||||
|
) => (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
className={'scroll-table'}
|
||||||
|
tableLayout={'auto'}
|
||||||
|
style={{ width: '100%', minHeight: 400 }}
|
||||||
|
dataSource={dataSource}
|
||||||
|
rowKey={(record) =>
|
||||||
|
`${record.metadata?.name}-${record.metadata?.namespace}`
|
||||||
|
}
|
||||||
|
loading={{
|
||||||
|
spinning: tableLoading,
|
||||||
|
size: 'middle'
|
||||||
|
}}
|
||||||
|
virtual
|
||||||
|
scroll={{ y: 'calc(100vh - 80px)' }}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tabItems: TabsProps['items'] = [
|
||||||
|
{
|
||||||
|
key: 'instance',
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.event.tab.instance'
|
||||||
|
}),
|
||||||
|
children: renderTable(instanceEvents, instanceLoading)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'volume',
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.event.tab.volume'
|
||||||
|
}),
|
||||||
|
children: renderTable(volumeEvents, volumeLoading)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const isLoading = instanceLoading || volumeLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollerModal
|
<ScrollerModal
|
||||||
title={
|
title={
|
||||||
@@ -155,8 +206,8 @@ const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
|||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<ReloadOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
onClick={fetchEvents}
|
onClick={refreshAll}
|
||||||
loading={loading}
|
loading={isLoading}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -188,23 +239,13 @@ const ViewEventsModal: React.FC<ViewEventsModalProps> = (props) => {
|
|||||||
contentStyle={{ paddingInline: 0 }}
|
contentStyle={{ paddingInline: 0 }}
|
||||||
></AlertBlockInfo>
|
></AlertBlockInfo>
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<Tabs
|
||||||
columns={columns}
|
size="small"
|
||||||
className={'scroll-table'}
|
type="card"
|
||||||
tableLayout={'auto'}
|
activeKey={activeKey}
|
||||||
style={{ width: '100%', minHeight: 400 }}
|
onChange={setActiveKey}
|
||||||
dataSource={events || []}
|
items={tabItems}
|
||||||
rowKey={(record) =>
|
/>
|
||||||
`${record.metadata?.name}-${record.metadata.namespace}`
|
|
||||||
}
|
|
||||||
loading={{
|
|
||||||
spinning: loading,
|
|
||||||
size: 'middle'
|
|
||||||
}}
|
|
||||||
virtual
|
|
||||||
scroll={{ y: 'calc(100vh - 80px)' }}
|
|
||||||
pagination={false}
|
|
||||||
></Table>
|
|
||||||
</ScrollerModal>
|
</ScrollerModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { currentClusterAtom } from '@/atoms/gpuservice';
|
||||||
|
import { getCurrentOrgNamespace } from '@/atoms/user';
|
||||||
|
import { useQueryData } from '@gpustack/core-ui';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { queryGPUServiceInstancePVEvents } from '../apis';
|
||||||
|
import { InstanceEvents } from '../config/types';
|
||||||
|
|
||||||
|
interface QueryVolumeEventsParams {
|
||||||
|
name: string;
|
||||||
|
pretty?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useQueryVolumeEvents() {
|
||||||
|
const currentCluster = useAtomValue(currentClusterAtom);
|
||||||
|
const clusterID = currentCluster?.id;
|
||||||
|
const namespace = getCurrentOrgNamespace(currentCluster?.owner_principal_id);
|
||||||
|
|
||||||
|
const fetchDetail = useCallback(
|
||||||
|
(params: QueryVolumeEventsParams, options?: any) =>
|
||||||
|
queryGPUServiceInstancePVEvents(
|
||||||
|
{
|
||||||
|
namespace,
|
||||||
|
clusterID,
|
||||||
|
name: params.name
|
||||||
|
},
|
||||||
|
options
|
||||||
|
),
|
||||||
|
[namespace, clusterID]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
|
||||||
|
InstanceEvents,
|
||||||
|
QueryVolumeEventsParams
|
||||||
|
>({
|
||||||
|
fetchDetail,
|
||||||
|
key: 'volumeEvents'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
detailData,
|
||||||
|
loading,
|
||||||
|
cancelRequest,
|
||||||
|
fetchData
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user