From f886422f915fa42a5fa0d46a7b1c95d3b95f13af Mon Sep 17 00:00:00 2001 From: jialin Date: Tue, 19 May 2026 14:10:11 +0800 Subject: [PATCH] fix(style): textarea label background --- src/pages/gpu-service/instances/apis/index.ts | 68 ++++++++ .../gpu-service/instances/config/index.ts | 33 ++++ .../gpu-service/instances/forms/basic.tsx | 17 +- src/pages/gpu-service/instances/index.tsx | 153 +++++++++++++++++- .../instances/styles/instances.module.less | 8 + .../gpu-service/templates/forms/basic.tsx | 35 ++-- 6 files changed, 286 insertions(+), 28 deletions(-) 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={ + + + 0 && ( + ({rowSelection.selectedRowKeys.length}) + ) + } + size="large" + showText={true} + disabled={!rowSelection.selectedRowKeys.length} + onSelect={handleBatchActionSelect} + /> + + } /> = ({ }))} /> - - name={['spec', 'command']} - normalize={normalizeCommand} - getValueProps={(value) => ({ value: stringifyCommand(value) })} - > -