feat: add events, log api

This commit is contained in:
jialin
2026-05-15 16:22:07 +08:00
committed by jialin
parent 1bef1637e0
commit 9dfaf990fd
8 changed files with 400 additions and 2 deletions
+57 -1
View File
@@ -1,6 +1,13 @@
import { request } from '@umijs/max';
import _ from 'lodash';
import { omitPathParams } from '../../utils';
import { InstanceTypeItem, ListItem } from '../config/types';
import {
InstanceEvents,
InstanceLog,
InstanceLogQueryParams,
InstanceTypeItem,
ListItem
} from '../config/types';
export const GPU_SERVICE_INSTANCES_API = (params: {
namespace: string;
@@ -127,6 +134,55 @@ export async function queryGPUServiceInstanceTypes(
);
}
export async function queryGPUServiceInstanceEvents(
params: {
namespace: string;
name: string;
clusterID?: number;
pretty?: string;
},
options?: any
) {
if (!params.clusterID) {
return;
}
return request<InstanceEvents>(
`${GPU_SERVICE_INSTANCES_API({
namespace: params.namespace,
clusterID: params.clusterID
})}/${params.name}/events`,
{
method: 'GET',
params: omitPathParams(_.omit(params, ['name'])),
cancelToken: options?.token
}
);
}
export async function queryGPUServiceInstanceLog(
params: InstanceLogQueryParams & {
namespace: string;
name: string;
clusterID?: number;
},
options?: any
) {
if (!params.clusterID) {
return;
}
return request<InstanceLog>(
`${GPU_SERVICE_INSTANCES_API({
namespace: params.namespace,
clusterID: params.clusterID
})}/${params.name}/log`,
{
method: 'GET',
params: omitPathParams(_.omit(params, ['name'])),
cancelToken: options?.token
}
);
}
export async function queryGPUServiceInstanceTypeItems(
params: {
name: string;
@@ -0,0 +1,106 @@
import { LogsViewer } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Modal } from 'antd';
import React, { useCallback, useEffect } from 'react';
type ViewModalProps = {
open: boolean;
url: string;
tail?: number;
status?: string;
onCancel: () => void;
};
const ViewLogsModal: React.FC<ViewModalProps> = (props) => {
const intl = useIntl();
const { open, onCancel, url } = props || {};
const logsViewerRef = React.useRef<any>(null);
const requestRef = React.useRef<any>(null);
const contentRef = React.useRef<any>(null);
const handleCancel = useCallback(() => {
logsViewerRef.current?.abort();
onCancel();
}, [onCancel]);
useEffect(() => {
const handleKeyDown = (e: any) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
e.preventDefault();
if (contentRef.current) {
const range = document.createRange();
range.selectNodeContents(contentRef.current);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
}
}
};
if (open) {
document.addEventListener('keydown', handleKeyDown);
}
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [open]);
useEffect(() => {
if (!url) return;
if (!open) {
logsViewerRef.current?.abort();
requestRef.current?.current?.cancel?.();
}
return () => {
logsViewerRef.current?.abort();
requestRef.current?.current?.cancel?.();
};
}, [url, open]);
return (
<Modal
title={
<span className="flex flex-center">
<span style={{ fontWeight: 'var(--font-weight-bold)' }}>
{intl.formatMessage({ id: 'common.button.viewlog' })}
</span>
</span>
}
zIndex={3000}
open={open}
centered={true}
onCancel={handleCancel}
destroyOnHidden={true}
closeIcon={true}
mask={{
closable: false
}}
keyboard={true}
styles={{
wrapper: {
borderRadius: 0
}
}}
width="100%"
footer={null}
>
<div ref={contentRef}>
<LogsViewer
ref={logsViewerRef}
diffHeight={78}
url={url}
tail={undefined}
enableScorllLoad={true}
isDownloading={false}
params={{
follow: true
}}
></LogsViewer>
</div>
</Modal>
);
};
export default ViewLogsModal;
@@ -1,7 +1,8 @@
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';
import { icons } from '@gpustack/core-ui';
import { IconFont, icons } from '@gpustack/core-ui';
import _ from 'lodash';
import React from 'react';
import {
InstanceTypeItem,
InstanceTypeResource,
@@ -45,6 +46,12 @@ export const status: Record<string, StatusType> = {
};
export const rowActionList = [
{
label: 'common.button.viewlog',
key: 'viewlog',
locale: true,
icon: React.createElement(IconFont, { type: 'icon-logs' })
},
{
label: 'common.button.edit',
key: 'edit',
@@ -134,3 +134,69 @@ export interface InstanceTypeItem {
spec: InstanceTypeSpec;
status: InstanceTypeStatus;
}
export interface InstanceEventObjectReference {
apiVersion?: string;
fieldPath?: string;
kind?: string;
name?: string;
namespace?: string;
resourceVersion?: string;
uid?: string;
}
export interface InstanceEventSource {
component?: string;
host?: string;
}
export interface InstanceEventSeries {
count?: number;
lastObservedTime?: string;
}
export interface InstanceEventItem {
action?: string;
apiVersion?: string;
count?: number;
eventTime?: string;
firstTimestamp?: string;
involvedObject: InstanceEventObjectReference;
kind?: string;
lastTimestamp?: string;
message?: string;
metadata: {
name?: string;
namespace?: string;
uid?: string;
resourceVersion?: string;
creationTimestamp?: string;
};
reason?: string;
related?: InstanceEventObjectReference;
reportingComponent?: string;
reportingInstance?: string;
series?: InstanceEventSeries;
source?: InstanceEventSource;
type?: string;
}
export interface InstanceEvents {
apiVersion?: string;
kind?: string;
metadata?: {
resourceVersion?: string;
};
items: InstanceEventItem[];
}
export interface InstanceLogQueryParams {
follow?: boolean;
limitBytes?: number;
sinceSeconds?: number;
tailLines?: number;
timestamps?: boolean;
pretty?: string;
}
export type InstanceLog = string;
@@ -0,0 +1,59 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { useAtomValue } from 'jotai';
import { useState } from 'react';
import { GPU_SERVICE_INSTANCES_API } from '../apis';
import { ListItem } from '../config/types';
const useViewLogs = () => {
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const clusterID = currentCluster?.id;
const [openModalStatus, setOpenModalStatus] = useState<{
open: boolean;
url: string;
tail?: number;
status?: string;
}>({
open: false,
url: '',
tail: 1000,
status: undefined
});
const openModal = (row?: ListItem) => {
const name = row?.metadata?.name;
const rowNamespace = row?.metadata?.namespace || namespace;
setOpenModalStatus({
open: true,
url:
name && clusterID
? `${GPU_SERVICE_INSTANCES_API({
namespace: rowNamespace,
clusterID
})}/${name}/log`
: '',
tail: 1000,
status: row?.status?.phase || undefined
});
};
const closeModal = () => {
setOpenModalStatus({
open: false,
url: '',
tail: 1000,
status: undefined
});
};
return {
openViewLogsModalStatus: openModalStatus,
setOpenViewLogsModalStatus: setOpenModalStatus,
openViewLogsModal: openModal,
closeViewLogsModal: closeModal
};
};
export default useViewLogs;
+12
View File
@@ -26,8 +26,10 @@ import {
queryGPUServiceInstances
} from './apis';
import AddModal from './components/add-modal';
import ViewLogsModal from './components/view-logs-modal';
import { FormData, ListItem } from './config/types';
import useInstancesColumns from './hooks/use-instances-columns';
import useViewLogs from './hooks/use-view-logs';
import useCreateInstance from './services/use-create-instance';
import useUpdateInstance from './services/use-update-instance';
@@ -105,6 +107,8 @@ const GPUService: React.FC = () => {
const { fetchData: createInstance } = useCreateInstance();
const { fetchData: updateInstance } = useUpdateInstance();
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
useViewLogs();
const {
fetchClusterList,
cancelRequest: cancelClusterRequest,
@@ -215,6 +219,8 @@ const GPUService: React.FC = () => {
name: row.metadata?.name,
id: row.metadata?.name as any
});
} else if (val === 'viewlog') {
openViewLogsModal(row);
}
});
@@ -329,6 +335,12 @@ const GPUService: React.FC = () => {
onCancel={closeModal}
onOk={handleModalOk}
/>
<ViewLogsModal
open={openViewLogsModalStatus.open}
url={openViewLogsModalStatus.url}
tail={openViewLogsModalStatus.tail}
onCancel={closeViewLogsModal}
/>
<DeleteModal ref={modalRef} />
</PageContainerInner>
);
@@ -0,0 +1,47 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { useQueryData } from '@gpustack/core-ui';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
import { queryGPUServiceInstanceEvents } from '../apis';
import { InstanceEvents } from '../config/types';
interface QueryInstanceEventsParams {
name: string;
pretty?: string;
}
export default function useQueryInstanceEvents() {
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const clusterID = currentCluster?.id;
const fetchDetail = useCallback(
(params: QueryInstanceEventsParams, options?: any) =>
queryGPUServiceInstanceEvents(
{
namespace,
clusterID,
name: params.name,
pretty: params.pretty
},
options
),
[namespace, clusterID]
);
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
InstanceEvents,
QueryInstanceEventsParams
>({
fetchDetail,
key: 'instanceEvents'
});
return {
detailData,
loading,
cancelRequest,
fetchData
};
}
@@ -0,0 +1,45 @@
import { currentClusterAtom } from '@/atoms/gpuservice';
import { getCurrentOrganizationId } from '@/atoms/user';
import { useQueryData } from '@gpustack/core-ui';
import { useAtomValue } from 'jotai';
import { useCallback } from 'react';
import { queryGPUServiceInstanceLog } from '../apis';
import { InstanceLog, InstanceLogQueryParams } from '../config/types';
interface QueryInstanceLogParams extends InstanceLogQueryParams {
name: string;
}
export default function useQueryInstanceLog() {
const namespace = getCurrentOrganizationId();
const currentCluster = useAtomValue(currentClusterAtom);
const clusterID = currentCluster?.id;
const fetchDetail = useCallback(
(params: QueryInstanceLogParams, options?: any) =>
queryGPUServiceInstanceLog(
{
...params,
namespace,
clusterID
},
options
),
[namespace, clusterID]
);
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
InstanceLog,
QueryInstanceLogParams
>({
fetchDetail,
key: 'instanceLog'
});
return {
detailData,
loading,
cancelRequest,
fetchData
};
}