diff --git a/src/locales/en-US/gpuservice.ts b/src/locales/en-US/gpuservice.ts
index 938aa806..88f2df4e 100644
--- a/src/locales/en-US/gpuservice.ts
+++ b/src/locales/en-US/gpuservice.ts
@@ -76,6 +76,8 @@ export default {
'gpuservice.instance.event.lastSeen': 'Last Seen',
'gpuservice.instance.event.recentHourTip':
'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.content':
'The current instance will be deleted first, then recreated with the current configuration.\n {name}',
diff --git a/src/locales/ja-JP/gpuservice.ts b/src/locales/ja-JP/gpuservice.ts
index e9ce2b3e..50887b7e 100644
--- a/src/locales/ja-JP/gpuservice.ts
+++ b/src/locales/ja-JP/gpuservice.ts
@@ -74,6 +74,8 @@ export default {
'gpuservice.instance.event.lastSeen': '最終発生',
'gpuservice.instance.event.recentHourTip':
'直近 1 時間のイベントのみ表示されます',
+ 'gpuservice.instance.event.tab.instance': 'インスタンスイベント',
+ 'gpuservice.instance.event.tab.volume': 'ボリュームイベント',
'gpuservice.instance.recreate.confirm.title': '再作成を確認しますか',
'gpuservice.instance.recreate.confirm.content':
'現在のインスタンスを削除した後、現在の構成で再作成します。\n {name}',
diff --git a/src/locales/ru-RU/gpuservice.ts b/src/locales/ru-RU/gpuservice.ts
index 79fbff5b..8c0d9fa1 100644
--- a/src/locales/ru-RU/gpuservice.ts
+++ b/src/locales/ru-RU/gpuservice.ts
@@ -75,6 +75,8 @@ export default {
'gpuservice.instance.event.lastSeen': 'Последнее событие',
'gpuservice.instance.event.recentHourTip':
'Отображаются только события за последний час',
+ 'gpuservice.instance.event.tab.instance': 'События экземпляра',
+ 'gpuservice.instance.event.tab.volume': 'События тома',
'gpuservice.instance.recreate.confirm.title': 'Подтвердить пересоздание',
'gpuservice.instance.recreate.confirm.content':
'Текущий экземпляр будет сначала удалён, а затем пересоздан с текущей конфигурацией.\n {name}',
diff --git a/src/locales/tr-TR/gpuservice.ts b/src/locales/tr-TR/gpuservice.ts
index 066ece9c..b4067925 100644
--- a/src/locales/tr-TR/gpuservice.ts
+++ b/src/locales/tr-TR/gpuservice.ts
@@ -74,6 +74,8 @@ export default {
'gpuservice.instance.event.lastSeen': 'Son Görülen',
'gpuservice.instance.event.recentHourTip':
'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':
'Yeniden oluşturma onaylansın mı',
'gpuservice.instance.recreate.confirm.content':
diff --git a/src/locales/zh-CN/gpuservice.ts b/src/locales/zh-CN/gpuservice.ts
index c589e2fe..9f436751 100644
--- a/src/locales/zh-CN/gpuservice.ts
+++ b/src/locales/zh-CN/gpuservice.ts
@@ -69,6 +69,8 @@ export default {
'gpuservice.instance.event.count': '次数',
'gpuservice.instance.event.lastSeen': '最近发生',
'gpuservice.instance.event.recentHourTip': '仅显示最近一小时的事件。',
+ 'gpuservice.instance.event.tab.instance': '实例事件',
+ 'gpuservice.instance.event.tab.volume': '存储卷事件',
'gpuservice.instance.recreate.confirm.title': '确认重新创建',
'gpuservice.instance.recreate.confirm.content':
'系统将先删除当前实例,然后使用当前配置重新创建。\n {name}',
diff --git a/src/pages/gpu-service/instances/apis/index.ts b/src/pages/gpu-service/instances/apis/index.ts
index c7099a69..c815a63e 100644
--- a/src/pages/gpu-service/instances/apis/index.ts
+++ b/src/pages/gpu-service/instances/apis/index.ts
@@ -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(
+ `${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(
params: InstanceLogQueryParams & {
namespace: string;
diff --git a/src/pages/gpu-service/instances/components/view-events-modal.tsx b/src/pages/gpu-service/instances/components/view-events-modal.tsx
index 6d9ed3b8..8e535b44 100644
--- a/src/pages/gpu-service/instances/components/view-events-modal.tsx
+++ b/src/pages/gpu-service/instances/components/view-events-modal.tsx
@@ -1,4 +1,3 @@
-import { createAxiosToken } from '@/hooks/use-chunk-request';
import { ReloadOutlined } from '@ant-design/icons';
import {
AlertBlockInfo,
@@ -7,12 +6,13 @@ import {
StatusTag
} from '@gpustack/core-ui';
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 dayjs from 'dayjs';
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { queryGPUServiceInstanceEvents } from '../apis';
+import React, { useEffect, useMemo, useState } from 'react';
import { InstanceEventItem } from '../config/types';
+import useQueryInstanceEvents from '../services/use-query-instance-events';
+import useQueryVolumeEvents from '../services/use-query-volume-events';
type ViewEventsModalProps = {
open: boolean;
@@ -31,119 +31,170 @@ const eventTypeStatus: Record = {
const ViewEventsModal: React.FC = (props) => {
const intl = useIntl();
- const { open, onCancel, name, namespace, clusterID } = props || {};
- const [events, setEvents] = useState([]);
- const [loading, setLoading] = useState(false);
- const tokenRef = useRef | null>(null);
+ const { open, onCancel, name } = props || {};
+ const [activeKey, setActiveKey] = useState('instance');
- const fetchEvents = useCallback(async () => {
- if (!name || !clusterID || !namespace) return;
- tokenRef.current?.cancel?.();
- const source = createAxiosToken();
- tokenRef.current = source;
- setLoading(true);
- try {
- const res = await queryGPUServiceInstanceEvents(
- { name, namespace, clusterID },
- { token: source.token }
- );
- setEvents(res?.items ?? []);
- } catch (e) {
- // request cancellation or network failure — keep prior items
- } finally {
- setLoading(false);
- }
- }, [name, namespace, clusterID]);
+ const {
+ detailData: instanceEventsData,
+ loading: instanceLoading,
+ cancelRequest: cancelInstanceRequest,
+ fetchData: fetchInstanceEvents
+ } = useQueryInstanceEvents();
+
+ const {
+ detailData: volumeEventsData,
+ loading: volumeLoading,
+ cancelRequest: cancelVolumeRequest,
+ fetchData: fetchVolumeEvents
+ } = useQueryVolumeEvents();
+
+ const instanceEvents = instanceEventsData?.items ?? [];
+ const volumeEvents = volumeEventsData?.items ?? [];
+
+ const refreshAll = () => {
+ if (!name) return;
+ fetchInstanceEvents({ name });
+ fetchVolumeEvents({ name });
+ };
useEffect(() => {
if (open) {
- fetchEvents();
+ refreshAll();
} else {
- tokenRef.current?.cancel?.();
- setEvents([]);
+ cancelInstanceRequest();
+ cancelVolumeRequest();
}
return () => {
- tokenRef.current?.cancel?.();
+ cancelInstanceRequest();
+ cancelVolumeRequest();
};
- }, [open, fetchEvents]);
+ }, [open]);
- const handleCancel = useCallback(() => {
- tokenRef.current?.cancel?.();
+ const handleCancel = () => {
+ cancelInstanceRequest();
+ cancelVolumeRequest();
onCancel();
- }, [onCancel]);
+ };
- const columns: ColumnsType = [
- {
- title: intl.formatMessage({ id: 'common.table.type' }),
- dataIndex: 'type',
- key: 'type',
- width: 110,
- render: (value: string) => (
-
- )
- },
- {
- title: intl.formatMessage({ id: 'gpuservice.instance.event.reason' }),
- dataIndex: 'reason',
- key: 'reason',
- width: 180,
- ellipsis: { showTitle: false },
- render: (text: string) => (
-
- {text || '-'}
-
- )
- },
- {
- title: intl.formatMessage({ id: 'gpuservice.instance.event.message' }),
- dataIndex: 'message',
- key: 'message',
- ellipsis: { showTitle: false },
- render: (text: string) => {text || '-'}
- },
- {
- 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 (
-
- {src}
-
- );
- }
- },
- {
- 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
+ const columns: ColumnsType = useMemo(
+ () => [
+ {
+ title: intl.formatMessage({ id: 'common.table.type' }),
+ dataIndex: 'type',
+ key: 'type',
+ width: 110,
+ render: (value: string) => (
+
)
+ },
+ {
+ title: intl.formatMessage({ id: 'gpuservice.instance.event.reason' }),
+ dataIndex: 'reason',
+ key: 'reason',
+ width: 180,
+ ellipsis: { showTitle: false },
+ render: (text: string) => (
+
+ {text || '-'}
+
+ )
+ },
+ {
+ title: intl.formatMessage({ id: 'gpuservice.instance.event.message' }),
+ dataIndex: 'message',
+ key: 'message',
+ ellipsis: { showTitle: false },
+ render: (text: string) => {text || '-'}
+ },
+ {
+ 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 (
+
+ {src}
+
+ );
+ }
+ },
+ {
+ 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
+ ) => (
+
+ `${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 (
= (props) => {
type="text"
size="small"
icon={}
- onClick={fetchEvents}
- loading={loading}
+ onClick={refreshAll}
+ loading={isLoading}
/>
}
@@ -188,23 +239,13 @@ const ViewEventsModal: React.FC = (props) => {
contentStyle={{ paddingInline: 0 }}
>
-
- `${record.metadata?.name}-${record.metadata.namespace}`
- }
- loading={{
- spinning: loading,
- size: 'middle'
- }}
- virtual
- scroll={{ y: 'calc(100vh - 80px)' }}
- pagination={false}
- >
+
);
};
diff --git a/src/pages/gpu-service/instances/services/use-query-volume-events.ts b/src/pages/gpu-service/instances/services/use-query-volume-events.ts
new file mode 100644
index 00000000..d9266994
--- /dev/null
+++ b/src/pages/gpu-service/instances/services/use-query-volume-events.ts
@@ -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
+ };
+}