style: subdrawer width
This commit is contained in:
@@ -4,7 +4,3 @@ import { atom } from 'jotai';
|
||||
export const currentClusterAtom = atom<
|
||||
(Partial<ClusterListItem> & { label?: string; value?: number }) | null
|
||||
>(null);
|
||||
|
||||
export const addSSHKeyPageAtom = atom<{ create: boolean }>({
|
||||
create: false
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export const InstanceStatusValueMap = {
|
||||
Preparing: 'Preparing',
|
||||
NotReady: 'NotReady',
|
||||
Ready: 'Ready',
|
||||
Staring: 'Staring'
|
||||
Starting: 'Starting'
|
||||
};
|
||||
|
||||
export const InstanceStatusLabelMap: Record<string, string> = {
|
||||
@@ -27,7 +27,7 @@ export const InstanceStatusLabelMap: Record<string, string> = {
|
||||
[InstanceStatusValueMap.Preparing]: 'Preparing',
|
||||
[InstanceStatusValueMap.NotReady]: 'NotReady',
|
||||
[InstanceStatusValueMap.Ready]: 'Ready',
|
||||
[InstanceStatusValueMap.Staring]: 'Staring'
|
||||
[InstanceStatusValueMap.Starting]: 'Starting'
|
||||
};
|
||||
|
||||
export const status: Record<string, StatusType> = {
|
||||
@@ -40,7 +40,7 @@ export const status: Record<string, StatusType> = {
|
||||
[InstanceStatusValueMap.Preparing]: StatusMaps.transitioning,
|
||||
[InstanceStatusValueMap.NotReady]: StatusMaps.error,
|
||||
[InstanceStatusValueMap.Ready]: StatusMaps.success,
|
||||
[InstanceStatusValueMap.Staring]: StatusMaps.transitioning
|
||||
[InstanceStatusValueMap.Starting]: StatusMaps.transitioning
|
||||
};
|
||||
|
||||
// Lifecycle actions (logs/events/start/stop) require K8s proxy endpoints that
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { addSSHKeyPageAtom } from '@/atoms/gpuservice';
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
@@ -14,9 +13,8 @@ import {
|
||||
useScrollActiveChange,
|
||||
useWrapperContext
|
||||
} from '@gpustack/core-ui';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { Button, Empty, Form } from 'antd';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Flex, Form } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
forwardRef,
|
||||
@@ -26,6 +24,8 @@ import {
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import { FormData as PublicKeyFormData } from '../../public-keys/config/types';
|
||||
import useCreateSshkey from '../../public-keys/services/use-create-sshkey';
|
||||
import useQuerySshkeys from '../../public-keys/services/use-query-sshkeys';
|
||||
import { DefaultImagePullPolicy } from '../../templates/config';
|
||||
import TemplateBasicForm, {
|
||||
@@ -36,6 +36,7 @@ import { FormData, InstanceTypeItem, ListItem } from '../config/types';
|
||||
import instanceStyles from '../styles/instances.module.less';
|
||||
import Basic from './basic';
|
||||
import InstanceTypeFormItem from './instance-type';
|
||||
import PublicKeyOverlay from './public-key-overlay';
|
||||
import StorageVolume from './storage-volume';
|
||||
|
||||
const SSH_PORT = 22;
|
||||
@@ -124,8 +125,6 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
onFinish
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const setAddSSHKeyPage = useSetAtom(addSSHKeyPageAtom);
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
const [form] = Form.useForm<InstanceFormValues>();
|
||||
const scrollTabsRef = useRef<any>(null);
|
||||
@@ -133,6 +132,8 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
realAction === PageAction.CREATE ? PageAction.CREATE : action;
|
||||
const sshEnabled = Form.useWatch('enable_ssh', form);
|
||||
const { sshkeyOptions, fetchData: fetchSSHData } = useQuerySshkeys();
|
||||
const { fetchData: createSshkey } = useCreateSshkey();
|
||||
const [sshOverlayOpen, setSshOverlayOpen] = useState(false);
|
||||
const { getScrollElementScrollableHeight } = useWrapperContext();
|
||||
const {
|
||||
activeKey,
|
||||
@@ -352,9 +353,23 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
}));
|
||||
|
||||
const handleAddSSHKey = () => {
|
||||
setAddSSHKeyPage({ create: true });
|
||||
// to go ssh key page
|
||||
navigate('/gpu-service/public-keys');
|
||||
setSshOverlayOpen(true);
|
||||
};
|
||||
|
||||
const handleCreateSSHKey = async (values: PublicKeyFormData) => {
|
||||
try {
|
||||
await fetchSSHData({ page: -1 });
|
||||
const current: Array<{ name: string } | string> =
|
||||
form.getFieldValue(['spec', 'sshPublicKeys']) || [];
|
||||
form.setFieldValue(
|
||||
['spec', 'sshPublicKeys'],
|
||||
[...current, { name: values.name }]
|
||||
);
|
||||
|
||||
setSshOverlayOpen(false);
|
||||
} catch (error) {
|
||||
// it's handled in interceptor
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnEnableSSHChange = (e: any) => {
|
||||
@@ -463,19 +478,31 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
]}
|
||||
/>
|
||||
|
||||
<Form.Item<FormData>
|
||||
name="enable_ssh"
|
||||
valuePropName="checked"
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<CheckboxField
|
||||
onChange={handleOnEnableSSHChange}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
id: 'gpuservice.instance.ssh.enable'
|
||||
<Flex align="center" justify="space-between">
|
||||
<Form.Item<FormData>
|
||||
name="enable_ssh"
|
||||
valuePropName="checked"
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<CheckboxField
|
||||
onChange={handleOnEnableSSHChange}
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage({
|
||||
id: 'gpuservice.instance.ssh.enable'
|
||||
})}
|
||||
></CheckboxField>
|
||||
</Form.Item>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddSSHKey}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.instance.ssh.addKey'
|
||||
})}
|
||||
></CheckboxField>
|
||||
</Form.Item>
|
||||
</Button>
|
||||
</Flex>
|
||||
{
|
||||
<div className={instanceStyles.sshkeySelection}>
|
||||
<Form.Item<FormData>
|
||||
@@ -515,38 +542,16 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
}
|
||||
}}
|
||||
options={sshkeyOptions}
|
||||
notFoundContent={
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={intl.formatMessage({
|
||||
id: 'noresult.gpuservice.sshkey.title'
|
||||
})}
|
||||
styles={{
|
||||
image: {
|
||||
height: 32
|
||||
},
|
||||
root: {
|
||||
margin: 0
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddSSHKey}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.instance.ssh.addKey'
|
||||
})}
|
||||
</Button>
|
||||
</Empty>
|
||||
}
|
||||
></MultipleSelect>
|
||||
</Form.Item>
|
||||
</div>
|
||||
}
|
||||
</Form>
|
||||
<PublicKeyOverlay
|
||||
open={sshOverlayOpen}
|
||||
onCancel={() => setSshOverlayOpen(false)}
|
||||
onSubmit={handleCreateSSHKey}
|
||||
/>
|
||||
</ScrollSpyTabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
|
||||
]}
|
||||
>
|
||||
<NumberSelection
|
||||
min={0}
|
||||
min={1}
|
||||
onChange={handleOnGPUCountChange}
|
||||
max={maxGpuCount}
|
||||
step={1}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { PageAction } from '@/config';
|
||||
import FormOverlayView from '@/pages/_components/form-overlay-view';
|
||||
import { ModalFooter } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useRef, useState } from 'react';
|
||||
import { FormData as PublicKeyFormData } from '../../public-keys/config/types';
|
||||
import GPUServicePublicKeyForm from '../../public-keys/forms';
|
||||
import useCreateSshkey from '../../public-keys/services/use-create-sshkey';
|
||||
import useOverlayLayout from '../hooks/use-overlay-layout';
|
||||
|
||||
interface PublicKeyOverlayProps {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onSubmit: (values: PublicKeyFormData) => Promise<void> | void;
|
||||
}
|
||||
|
||||
const PublicKeyOverlay: React.FC<PublicKeyOverlayProps> = ({
|
||||
open,
|
||||
onCancel,
|
||||
onSubmit
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const formRef = useRef<any>(null);
|
||||
const { fetchData: createSshkey } = useCreateSshkey();
|
||||
const { drawerWidth, getOverlayContainer } = useOverlayLayout(open);
|
||||
|
||||
const handleSubmit = () => {
|
||||
formRef.current?.submit();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
formRef.current?.resetFields();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
const handleFinish = async (values: PublicKeyFormData) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await createSshkey({ data: values });
|
||||
await onSubmit(values);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormOverlayView
|
||||
title={intl.formatMessage({ id: 'gpuservice.publicKey.add' })}
|
||||
open={open}
|
||||
width={drawerWidth}
|
||||
onCancel={handleCancel}
|
||||
getContainer={getOverlayContainer}
|
||||
footer={
|
||||
<ModalFooter
|
||||
onOk={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
loading={loading}
|
||||
style={{
|
||||
padding: '16px 24px 24px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end'
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<GPUServicePublicKeyForm
|
||||
ref={formRef}
|
||||
action={PageAction.CREATE}
|
||||
open={open}
|
||||
onFinish={handleFinish}
|
||||
/>
|
||||
</FormOverlayView>
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicKeyOverlay;
|
||||
@@ -4,9 +4,10 @@ import { FormContext } from '@/pages/gpu-service/storage/config/form-context';
|
||||
import useQueryStorageClass from '@/pages/gpu-service/storage/services/use-query-storage-class';
|
||||
import { ModalFooter } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { FormData as StorageFormData } from '../../storage/config/types';
|
||||
import GPUServiceStorageForm from '../../storage/forms';
|
||||
import useOverlayLayout from '../hooks/use-overlay-layout';
|
||||
|
||||
interface StorageOverlayProps {
|
||||
open: boolean;
|
||||
@@ -24,6 +25,7 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
||||
useQueryStorageClass();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const formRef = useRef<any>(null);
|
||||
const { drawerWidth, getOverlayContainer } = useOverlayLayout(open);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
@@ -49,19 +51,11 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getOverlayContainer = useCallback(() => {
|
||||
const containers = document.querySelectorAll<HTMLElement>(
|
||||
'.ant-layout-content'
|
||||
);
|
||||
|
||||
return containers[containers.length - 1] ?? null;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FormOverlayView
|
||||
title={intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||
open={open}
|
||||
width={600}
|
||||
width={drawerWidth}
|
||||
onCancel={handleCancel}
|
||||
getContainer={getOverlayContainer}
|
||||
footer={
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PageAction } from '@/config';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Input as CInput,
|
||||
InputNumber as CInputNumber,
|
||||
@@ -135,6 +136,7 @@ const StorageVolume = ({
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setOverlayOpen(true)}
|
||||
>
|
||||
{intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
DropdownButtons,
|
||||
StatusTag
|
||||
} from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { useAccess, useIntl } from '@umijs/max';
|
||||
import { Button, Flex } from 'antd';
|
||||
import type { ColumnsType } from 'antd/lib/table';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -80,6 +80,7 @@ const useInstancesColumns = ({
|
||||
sortOrder
|
||||
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
||||
const intl = useIntl();
|
||||
const access = useAccess();
|
||||
|
||||
const renderInstanceType = (record: ListItem) => {
|
||||
const description = JSON.parse(record?.description || '{}').spec || {};
|
||||
@@ -122,7 +123,7 @@ const useInstancesColumns = ({
|
||||
},
|
||||
render: (text: string, record: ListItem) => (
|
||||
<AutoTooltip ghost style={{ maxWidth: 360 }}>
|
||||
<span className="text-primary">{text || record.displayName}</span>
|
||||
<span className="text-primary">{record.displayName || text}</span>
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
@@ -247,6 +248,7 @@ const useInstancesColumns = ({
|
||||
{
|
||||
title: intl.formatMessage({ id: 'clusters.title' }),
|
||||
dataIndex: 'clusterId',
|
||||
hidden: !access.canSeeAdmin,
|
||||
render: (id: number) => (
|
||||
<AutoTooltip ghost maxWidth={240}>
|
||||
{_.find(clusterList, { value: id })?.label || id || '-'}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import instanceStyles from '../styles/instances.module.less';
|
||||
|
||||
const FALLBACK_WIDTH = `calc((100vw - 220px) / 3 + 16px)`;
|
||||
|
||||
const useOverlayLayout = (open: boolean) => {
|
||||
const [drawerWidth, setDrawerWidth] = useState<number | string>(
|
||||
FALLBACK_WIDTH
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const formWrapper = document.querySelector<HTMLElement>(
|
||||
`.${instanceStyles.formWrapper}`
|
||||
);
|
||||
if (formWrapper) {
|
||||
setDrawerWidth(formWrapper.clientWidth);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const getOverlayContainer = useCallback(() => {
|
||||
const containers = document.querySelectorAll<HTMLElement>(
|
||||
'.ant-layout-content'
|
||||
);
|
||||
return containers[containers.length - 1] ?? null;
|
||||
}, []);
|
||||
|
||||
return { drawerWidth, getOverlayContainer };
|
||||
};
|
||||
|
||||
export default useOverlayLayout;
|
||||
@@ -67,6 +67,7 @@ const usePublicKeyColumns = ({
|
||||
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||
key: 'operation',
|
||||
dataIndex: 'operation',
|
||||
width: 200,
|
||||
render: (_text, record) => (
|
||||
<DropdownButtons
|
||||
items={rowActionList}
|
||||
|
||||
Reference in New Issue
Block a user