diff --git a/src/pages/gpu-service/instances/apis/index.ts b/src/pages/gpu-service/instances/apis/index.ts
index c815a63e..9dff0b68 100644
--- a/src/pages/gpu-service/instances/apis/index.ts
+++ b/src/pages/gpu-service/instances/apis/index.ts
@@ -45,6 +45,24 @@ export const GPU_SERVICE_INSTANCE_PV_EVENTS_API = (params: {
return `/clusters/${params.clusterID}/proxy/apis/worker.gpustack.ai/v1/namespaces/${params.namespace}/instancepersistentvolumes/${params.name}/events`;
};
+// TODO: replace placeholder once start/stop endpoints are finalized
+export const GPU_SERVICE_INSTANCE_START_API = (params: {
+ namespace: string;
+ name: string;
+ clusterID?: number;
+}) => {
+ return `/clusters/${params.clusterID}/proxy/apis/worker.gpustack.ai/v1/namespaces/${params.namespace}/instances/${params.name}/start`;
+};
+
+// TODO: replace placeholder once start/stop endpoints are finalized
+export const GPU_SERVICE_INSTANCE_STOP_API = (params: {
+ namespace: string;
+ name: string;
+ clusterID?: number;
+}) => {
+ return `/clusters/${params.clusterID}/proxy/apis/worker.gpustack.ai/v1/namespaces/${params.namespace}/instances/${params.name}/stop`;
+};
+
// =========== Instances ===========
export async function queryGPUServiceInstances(
@@ -212,6 +230,56 @@ export async function queryGPUServiceInstancePVEvents(
);
}
+// TODO: replace placeholder once start/stop endpoints are finalized
+export async function startGPUServiceInstance(
+ params: {
+ namespace: string;
+ name: string;
+ clusterID?: number;
+ },
+ option?: any
+) {
+ if (!params.clusterID) {
+ return;
+ }
+ return request(
+ GPU_SERVICE_INSTANCE_START_API({
+ namespace: params.namespace,
+ clusterID: params.clusterID,
+ name: params.name
+ }),
+ {
+ method: 'POST',
+ cancelToken: option?.token
+ }
+ );
+}
+
+// TODO: replace placeholder once start/stop endpoints are finalized
+export async function stopGPUServiceInstance(
+ params: {
+ namespace: string;
+ name: string;
+ clusterID?: number;
+ },
+ option?: any
+) {
+ if (!params.clusterID) {
+ return;
+ }
+ return request(
+ GPU_SERVICE_INSTANCE_STOP_API({
+ namespace: params.namespace,
+ clusterID: params.clusterID,
+ name: params.name
+ }),
+ {
+ method: 'POST',
+ cancelToken: option?.token
+ }
+ );
+}
+
export async function queryGPUServiceInstanceLog(
params: InstanceLogQueryParams & {
namespace: string;
diff --git a/src/pages/gpu-service/instances/config/index.ts b/src/pages/gpu-service/instances/config/index.ts
index 2c62593e..c6975d13 100644
--- a/src/pages/gpu-service/instances/config/index.ts
+++ b/src/pages/gpu-service/instances/config/index.ts
@@ -64,6 +64,18 @@ export const rowActionList = [
locale: true,
icon: icons.ProfileOutlined
},
+ {
+ label: 'common.button.start',
+ key: 'start',
+ locale: true,
+ icon: icons.Play
+ },
+ {
+ label: 'common.button.stop',
+ key: 'stop',
+ locale: true,
+ icon: icons.Stop
+ },
// {
// label: 'common.button.edit',
// key: 'edit',
@@ -87,6 +99,27 @@ export const rowActionList = [
}
];
+export const batchActionList = [
+ {
+ label: 'common.button.start',
+ key: 'start',
+ icon: icons.Play
+ },
+ {
+ label: 'common.button.stop',
+ key: 'stop',
+ icon: icons.Stop
+ },
+ {
+ label: 'common.button.delete',
+ key: 'delete',
+ icon: icons.DeleteOutlined,
+ props: {
+ danger: true
+ }
+ }
+];
+
export const InstanceTypePhaseValueMap = {
// Available: 'Available',
// Unavailable: 'Unavailable'
diff --git a/src/pages/gpu-service/instances/forms/basic.tsx b/src/pages/gpu-service/instances/forms/basic.tsx
index 011f2448..9da367fb 100644
--- a/src/pages/gpu-service/instances/forms/basic.tsx
+++ b/src/pages/gpu-service/instances/forms/basic.tsx
@@ -4,6 +4,7 @@ import { Input as CInput } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { FormData } from '../config/types';
+import formStyles from '../styles/instances.module.less';
const Basic = ({
action,
@@ -37,13 +38,15 @@ const Basic = ({
required
/>
-
name={['spec', 'description']}>
-
-
+
+
name={['spec', 'description']}>
+
+
+
>
);
};
diff --git a/src/pages/gpu-service/instances/index.tsx b/src/pages/gpu-service/instances/index.tsx
index 691812eb..bd31a1fa 100644
--- a/src/pages/gpu-service/instances/index.tsx
+++ b/src/pages/gpu-service/instances/index.tsx
@@ -5,16 +5,28 @@ import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useTableFetch from '@/hooks/use-table-fetch';
import { ProviderValueMap } from '@/pages/cluster-management/config';
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
+import { handleBatchRequest } from '@/utils';
+import { PlusOutlined } from '@ant-design/icons';
import {
BaseSelect,
DeleteModal,
+ DropdownButtons,
FilterBar,
IconFont,
NoResult
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
-import { ConfigProvider, Divider, Flex, message, Modal, Table } from 'antd';
+import {
+ Button,
+ ConfigProvider,
+ Divider,
+ Flex,
+ message,
+ Modal,
+ Space,
+ Table
+} from 'antd';
import { useAtom } from 'jotai';
import _ from 'lodash';
import { useCallback, useEffect, useMemo } from 'react';
@@ -22,11 +34,14 @@ import { PageContainerInner } from '../../_components/page-box';
import {
deleteGPUServiceInstance,
GPU_SERVICE_INSTANCES_API,
- queryGPUServiceInstances
+ queryGPUServiceInstances,
+ startGPUServiceInstance,
+ stopGPUServiceInstance
} from './apis';
import AddModal from './components/add-modal';
import ViewEventsModal from './components/view-events-modal';
import ViewLogsModal from './components/view-logs-modal';
+import { batchActionList } from './config';
import { FormData, ListItem } from './config/types';
import useCreateInstance from './hooks/use-create-instance';
import useInstancesColumns from './hooks/use-instances-columns';
@@ -49,6 +64,26 @@ const GPUService: React.FC = () => {
[namespace, clusterID]
);
+ const startInstance = useCallback(
+ (row: ListItem) =>
+ startGPUServiceInstance({
+ namespace: row.metadata?.namespace || namespace,
+ clusterID,
+ name: row.metadata?.name as string
+ }),
+ [namespace, clusterID]
+ );
+
+ const stopInstance = useCallback(
+ (row: ListItem) =>
+ stopGPUServiceInstance({
+ namespace: row.metadata?.namespace || namespace,
+ clusterID,
+ name: row.metadata?.name as string
+ }),
+ [namespace, clusterID]
+ );
+
const fetchInstances = useMemoizedFn(
async (
params: any,
@@ -225,6 +260,80 @@ const GPUService: React.FC = () => {
});
});
+ const handleStart = useMemoizedFn(async (row: ListItem) => {
+ modalRef.current?.show({
+ title: intl.formatMessage({ id: 'common.title.start.confirm' }),
+ content: 'gpuservice.instance',
+ okText: 'common.button.start',
+ operation: 'common.start.single.confirm',
+ name: row.metadata?.name,
+ async onOk() {
+ try {
+ await startInstance(row);
+ message.success(intl.formatMessage({ id: 'common.message.success' }));
+ fetchData();
+ } catch (error) {
+ // ignore
+ }
+ }
+ });
+ });
+
+ const handleStop = useMemoizedFn(async (row: ListItem) => {
+ modalRef.current?.show({
+ title: intl.formatMessage({ id: 'common.title.stop.confirm' }),
+ content: 'gpuservice.instance',
+ okText: 'common.button.stop',
+ operation: 'common.stop.single.confirm',
+ name: row.metadata?.name,
+ async onOk() {
+ try {
+ await stopInstance(row);
+ message.success(intl.formatMessage({ id: 'common.message.success' }));
+ fetchData();
+ } catch (error) {
+ // ignore
+ }
+ }
+ });
+ });
+
+ const handleStartBatch = useMemoizedFn(() => {
+ modalRef.current?.show({
+ title: intl.formatMessage({ id: 'common.title.start.confirm' }),
+ content: 'gpuservice.instance',
+ okText: 'common.button.start',
+ operation: 'common.start.confirm',
+ selection: true,
+ async onOk() {
+ const res = await handleBatchRequest(
+ rowSelection.selectedRows as ListItem[],
+ startInstance
+ );
+ fetchData();
+ return res;
+ }
+ });
+ });
+
+ const handleStopBatch = useMemoizedFn(() => {
+ modalRef.current?.show({
+ title: intl.formatMessage({ id: 'common.title.stop.confirm' }),
+ content: 'gpuservice.instance',
+ okText: 'common.button.stop',
+ operation: 'common.stop.confirm',
+ selection: true,
+ async onOk() {
+ const res = await handleBatchRequest(
+ rowSelection.selectedRows as ListItem[],
+ stopInstance
+ );
+ fetchData();
+ return res;
+ }
+ });
+ });
+
const handleSelect = useMemoizedFn((val: string, row: ListItem) => {
if (val === 'view') {
openViewInstanceModal(row);
@@ -244,6 +353,20 @@ const GPUService: React.FC = () => {
openViewLogsModal(row);
} else if (val === 'viewevent') {
openViewEventsModal(row);
+ } else if (val === 'start') {
+ handleStart(row);
+ } else if (val === 'stop') {
+ handleStop(row);
+ }
+ });
+
+ const handleBatchActionSelect = useMemoizedFn((val: string) => {
+ if (val === 'start') {
+ handleStartBatch();
+ } else if (val === 'stop') {
+ handleStopBatch();
+ } else if (val === 'delete') {
+ handleDeleteBatch();
}
});
@@ -317,14 +440,34 @@ const GPUService: React.FC = () => {
selectHolder={intl.formatMessage({
id: 'gpuservice.instance.filter.cluster'
})}
- buttonText={intl.formatMessage({ id: 'gpuservice.instance.add' })}
handleSearch={handleSearch}
handleSelectChange={handleClusterChange}
- handleDeleteByBatch={handleDeleteBatch}
- handleClickPrimary={openCreateInstanceModal}
handleInputChange={handleNameChange}
rowSelection={rowSelection}
widths={{ input: 300 }}
+ right={
+
+ }
+ type="primary"
+ onClick={openCreateInstanceModal}
+ >
+ {intl.formatMessage({ id: 'gpuservice.instance.add' })}
+
+ 0 && (
+ ({rowSelection.selectedRowKeys.length})
+ )
+ }
+ size="large"
+ showText={true}
+ disabled={!rowSelection.selectedRowKeys.length}
+ onSelect={handleBatchActionSelect}
+ />
+
+ }
/>
= ({
}))}
/>
-
- name={['spec', 'command']}
- normalize={normalizeCommand}
- getValueProps={(value) => ({ value: stringifyCommand(value) })}
- >
-
-
+
+
+ name={['spec', 'command']}
+ normalize={normalizeCommand}
+ getValueProps={(value) => ({ value: stringifyCommand(value) })}
+ >
+
+
+