feat: add events, log api
This commit is contained in:
@@ -1,6 +1,13 @@
|
|||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
|
import _ from 'lodash';
|
||||||
import { omitPathParams } from '../../utils';
|
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: {
|
export const GPU_SERVICE_INSTANCES_API = (params: {
|
||||||
namespace: string;
|
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(
|
export async function queryGPUServiceInstanceTypeItems(
|
||||||
params: {
|
params: {
|
||||||
name: string;
|
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 { StatusMaps } from '@/config';
|
||||||
import { StatusType } from '@/config/types';
|
import { StatusType } from '@/config/types';
|
||||||
import { icons } from '@gpustack/core-ui';
|
import { IconFont, icons } from '@gpustack/core-ui';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
InstanceTypeItem,
|
InstanceTypeItem,
|
||||||
InstanceTypeResource,
|
InstanceTypeResource,
|
||||||
@@ -45,6 +46,12 @@ export const status: Record<string, StatusType> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const rowActionList = [
|
export const rowActionList = [
|
||||||
|
{
|
||||||
|
label: 'common.button.viewlog',
|
||||||
|
key: 'viewlog',
|
||||||
|
locale: true,
|
||||||
|
icon: React.createElement(IconFont, { type: 'icon-logs' })
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'common.button.edit',
|
label: 'common.button.edit',
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
|
|||||||
@@ -134,3 +134,69 @@ export interface InstanceTypeItem {
|
|||||||
spec: InstanceTypeSpec;
|
spec: InstanceTypeSpec;
|
||||||
status: InstanceTypeStatus;
|
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;
|
||||||
@@ -26,8 +26,10 @@ import {
|
|||||||
queryGPUServiceInstances
|
queryGPUServiceInstances
|
||||||
} from './apis';
|
} from './apis';
|
||||||
import AddModal from './components/add-modal';
|
import AddModal from './components/add-modal';
|
||||||
|
import ViewLogsModal from './components/view-logs-modal';
|
||||||
import { FormData, ListItem } from './config/types';
|
import { FormData, ListItem } from './config/types';
|
||||||
import useInstancesColumns from './hooks/use-instances-columns';
|
import useInstancesColumns from './hooks/use-instances-columns';
|
||||||
|
import useViewLogs from './hooks/use-view-logs';
|
||||||
import useCreateInstance from './services/use-create-instance';
|
import useCreateInstance from './services/use-create-instance';
|
||||||
import useUpdateInstance from './services/use-update-instance';
|
import useUpdateInstance from './services/use-update-instance';
|
||||||
|
|
||||||
@@ -105,6 +107,8 @@ const GPUService: React.FC = () => {
|
|||||||
|
|
||||||
const { fetchData: createInstance } = useCreateInstance();
|
const { fetchData: createInstance } = useCreateInstance();
|
||||||
const { fetchData: updateInstance } = useUpdateInstance();
|
const { fetchData: updateInstance } = useUpdateInstance();
|
||||||
|
const { openViewLogsModal, closeViewLogsModal, openViewLogsModalStatus } =
|
||||||
|
useViewLogs();
|
||||||
const {
|
const {
|
||||||
fetchClusterList,
|
fetchClusterList,
|
||||||
cancelRequest: cancelClusterRequest,
|
cancelRequest: cancelClusterRequest,
|
||||||
@@ -215,6 +219,8 @@ const GPUService: React.FC = () => {
|
|||||||
name: row.metadata?.name,
|
name: row.metadata?.name,
|
||||||
id: row.metadata?.name as any
|
id: row.metadata?.name as any
|
||||||
});
|
});
|
||||||
|
} else if (val === 'viewlog') {
|
||||||
|
openViewLogsModal(row);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -329,6 +335,12 @@ const GPUService: React.FC = () => {
|
|||||||
onCancel={closeModal}
|
onCancel={closeModal}
|
||||||
onOk={handleModalOk}
|
onOk={handleModalOk}
|
||||||
/>
|
/>
|
||||||
|
<ViewLogsModal
|
||||||
|
open={openViewLogsModalStatus.open}
|
||||||
|
url={openViewLogsModalStatus.url}
|
||||||
|
tail={openViewLogsModalStatus.tail}
|
||||||
|
onCancel={closeViewLogsModal}
|
||||||
|
/>
|
||||||
<DeleteModal ref={modalRef} />
|
<DeleteModal ref={modalRef} />
|
||||||
</PageContainerInner>
|
</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
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user