fix: style issues

This commit is contained in:
jialin
2026-05-11 19:49:21 +08:00
committed by jialin
parent 1b74285de9
commit 01fe2e66fb
33 changed files with 296 additions and 137 deletions
@@ -24,7 +24,7 @@ const Container = styled.div`
const ColWrapper = styled.div`
display: flex;
flex: 1;
max-width: 33.33%;
max-width: 33%;
min-height: 0;
`;
@@ -39,7 +39,7 @@ const PanelBody = styled.div`
const FormWrapper = styled.div`
display: flex;
flex: 1;
max-width: 33.33%;
max-width: 34%;
min-height: 0;
`;
@@ -52,6 +52,30 @@ type AddModalProps = {
onCancel: () => void;
};
const ColTitle: React.FC<{
children: React.ReactNode;
style?: React.CSSProperties;
}> = ({ children, style }) => {
return (
<Typography.Title
level={3}
style={{
fontSize: 14,
paddingTop: 10,
paddingBottom: 16,
margin: 0,
position: 'sticky',
top: 0,
zIndex: 100,
backgroundColor: 'var(--ant-color-bg-elevated)',
...style
}}
>
{children}
</Typography.Title>
);
};
const AddModal: React.FC<AddModalProps> = ({
title,
action,
@@ -81,7 +105,7 @@ const AddModal: React.FC<AddModalProps> = ({
if (open) {
autoSelectedRef.current = false;
fetchData({});
fetchTemplates({ page: 1, perPage: 100 });
fetchTemplates({ page: -1 });
} else {
setSelectedInstanceType(undefined);
setSelectedManufacturer(undefined);
@@ -112,6 +136,11 @@ const AddModal: React.FC<AddModalProps> = ({
}, [instanceKeyword, instanceTypeList]);
const manufacturerMatchedTemplates = useMemo(() => {
console.log(
'Filtering templates by manufacturer:',
templateList,
selectedManufacturer
);
if (!selectedManufacturer) {
return templateList;
}
@@ -277,6 +306,9 @@ const AddModal: React.FC<AddModalProps> = ({
<ColWrapper>
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
<PanelBody>
<ColTitle style={{ paddingBottom: 0 }}>
{intl.formatMessage({ id: 'gpuservice.instance.types' })}
</ColTitle>
<div
style={{
position: 'sticky',
@@ -308,6 +340,9 @@ const AddModal: React.FC<AddModalProps> = ({
<ColWrapper>
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
<PanelBody>
<ColTitle style={{ paddingBottom: 0 }}>
{intl.formatMessage({ id: 'gpuservice.instance.templates' })}
</ColTitle>
<div
style={{
position: 'sticky',
@@ -355,27 +390,16 @@ const AddModal: React.FC<AddModalProps> = ({
}
>
<>
<Typography.Title
level={3}
style={{
fontSize: 14,
paddingTop: 10,
paddingBottom: 16,
margin: 0,
position: 'sticky',
top: 0,
zIndex: 100,
backgroundColor: 'var(--ant-color-bg-elevated)'
}}
>
Configuration
</Typography.Title>
<ColTitle>
{intl.formatMessage({ id: 'common.title.config' })}
</ColTitle>
<GPUServiceInstanceForm
ref={form}
action={action}
currentData={data}
onFinish={onFinish}
open={open}
instanceTypeList={instanceTypeList}
/>
</>
</ColumnWrapper>
@@ -115,16 +115,38 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
<span>{convertKiToGi(item.spec?.memory) ?? '-'}</span>
</Flex>
</span>
<span className="dot"></span>
<span className="meta-item">
<IconFont type="icon-cube" className="meta-icon" />
<Flex align="center" gap={4}>
<span>
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}
{item.spec?.sliced && (
<>
<span className="dot"></span>
<span className="meta-item">
<IconFont type="icon-sliced" className="meta-icon" />
<Flex align="center" gap={4}>
<span>
{intl.formatMessage({
id: 'gpuservice.instance.sliced'
})}
</span>
<span>{item.spec?.sliced ?? '-'}</span>
</Flex>
</span>
<span>{item.status?.accelerator?.remaining ?? '-'}</span>
</Flex>
</span>
</>
)}
{item.status?.accelerator?.remaining && (
<>
<span className="dot"></span>
<span className="meta-item">
<IconFont type="icon-cube" className="meta-icon" />
<Flex align="center" gap={4}>
<span>
{intl.formatMessage({
id: 'gpuservice.instance.stock'
})}
</span>
<span>{item.status?.accelerator?.remaining ?? '-'}</span>
</Flex>
</span>
</>
)}
</span>
)}
</span>
@@ -130,6 +130,10 @@ export const transformInstanceType = (
const status = item.status;
return {
...item,
spec: {
...item.spec,
manufacturer: !item.spec?.acceleratable ? 'cpu' : item.spec.manufacturer
},
status: {
...status,
accelerator: transformInstanceTypeResource(
@@ -20,8 +20,10 @@ import {
} from 'react';
import useGetSshkey from '../../public-keys/services/use-get-sshkey';
import { DefaultImagePullPolicy } from '../../templates/config';
import TemplateBasicForm from '../../templates/forms/basic';
import { FormData, InstancePort, ListItem } from '../config/types';
import TemplateBasicForm, {
BasicResourceMax
} from '../../templates/forms/basic';
import { FormData, InstanceTypeItem, ListItem } from '../config/types';
import Basic from './basic';
import InstanceTypeFormItem from './instance-type';
import StorageVolume from './storage-volume';
@@ -39,9 +41,18 @@ interface InstanceFormProps {
action: PageActionType;
currentData?: ListItem | null;
namespace?: string;
instanceTypeList?: InstanceTypeItem[];
onFinish: (values: FormData) => Promise<void>;
}
const parseQuantityToNumber = (value?: string): number | null => {
if (!value) return null;
const match = /^(-?\d+(?:\.\d+)?)/.exec(String(value));
if (!match) return null;
const num = Number(match[1]);
return Number.isFinite(num) && num > 0 ? num : null;
};
const TABKeysMap = {
BASIC: 'basic',
INSTANCE_TYPE: 'instanceType',
@@ -75,6 +86,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
currentData,
open,
namespace = 'default',
instanceTypeList = [],
onFinish
} = props;
const intl = useIntl();
@@ -108,7 +120,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
{
value: TABKeysMap.BASIC,
label: intl.formatMessage({
id: 'gpuservice.instance.section.basic'
id: 'common.title.basicInfo'
}),
icon: <IconFont type="icon-basic" />,
field: 'name'
@@ -139,14 +151,31 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
[intl]
);
const ports = (Form.useWatch(['spec', 'ports'], form) ||
[]) as InstancePort[];
const ports = Form.useWatch(['spec', 'ports'], form) || [];
const hasSshPort = useMemo(
() =>
ports.some((p) => Number(p?.port) === SSH_PORT && p.protocol === 'TCP'),
[ports]
);
const selectedTypeName = Form.useWatch(['spec', 'type'], form) as
| string
| undefined;
const onceMaxRequest = useMemo<BasicResourceMax>(() => {
const selected = instanceTypeList.find(
(item) => (item.metadata?.name || '') === selectedTypeName
);
const status = selected?.status;
return {
cpu: parseQuantityToNumber(status?.cpu?.onceMaxRequest),
memory: parseQuantityToNumber(status?.ram?.onceMaxRequest),
localStorage: parseQuantityToNumber(
status?.localStorage?.onceMaxRequest
)
};
}, [instanceTypeList, selectedTypeName]);
const onTargetChange = (key: string) => {
scrollTabsRef.current?.handleTargetChange(key);
};
@@ -277,7 +306,12 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
id: 'gpuservice.instance.section.template'
}),
forceRender: true,
children: <TemplateBasicForm page="instance" />
children: (
<TemplateBasicForm
page="instance"
onceMaxRequest={onceMaxRequest}
/>
)
},
{
key: TABKeysMap.STORAGE,
@@ -1,6 +1,6 @@
import { InputNumber as CInputNumber } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { Flex, Form } from 'antd';
import { useEffect, useMemo } from 'react';
import styled from 'styled-components';
import InstanceTypeItem from '../components/instance-type-item';
@@ -138,7 +138,19 @@ const InstanceTypeFormItem = () => {
min={1}
max={maxGpuCount}
precision={0}
label={intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })}
label={
<Flex gap={4} align="center">
{intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })}
<span>
(
{intl.formatMessage(
{ id: 'common.max' },
{ count: maxGpuCount }
)}
)
</span>
</Flex>
}
required
/>
</Form.Item>
@@ -33,7 +33,7 @@ const TemplateContent = styled.div`
}
.value {
color: var(--ant-color-text);
color: var(--ant-color-text-secondary);
}
.icon {
@@ -134,7 +134,7 @@ const useInstancesColumns = ({
backgroundColor: 'var(--ant-color-fill-tertiary)'
}}
>
{entry.port}
<span>{entry.port}</span>
<ExportOutlined style={{ fontSize: 10, marginLeft: 4 }} />
</Button>
</Flex>
+4 -1
View File
@@ -116,7 +116,10 @@ export async function queryStorageClass(
{
method: 'GET',
params: omitPathParams(params),
cancelToken: options?.token
cancelToken: options?.token,
headers: {
skipErrorHandler: 'true'
}
}
);
}
+3 -12
View File
@@ -28,10 +28,7 @@ const Basic = ({ open }: { open: boolean }) => {
rules={[
{
required: true,
message: getRuleMessage(
'input',
intl.formatMessage({ id: 'common.table.name' })
)
message: getRuleMessage('input', 'common.table.name')
}
]}
>
@@ -47,10 +44,7 @@ const Basic = ({ open }: { open: boolean }) => {
rules={[
{
required: true,
message: getRuleMessage(
'select',
intl.formatMessage({ id: 'gpuservice.storage.type' })
)
message: getRuleMessage('select', 'gpuservice.storage.type')
}
]}
>
@@ -72,10 +66,7 @@ const Basic = ({ open }: { open: boolean }) => {
rules={[
{
required: true,
message: getRuleMessage(
'input',
intl.formatMessage({ id: 'gpuservice.storage.capacity' })
)
message: getRuleMessage('input', 'gpuservice.storage.capacity')
}
]}
>
@@ -52,7 +52,7 @@ const useStorageColumns = ({
dataIndex: ['spec', 'capacity'],
key: 'capacity',
sorter: false,
render: (value: string) => value ?? '-'
render: (value: string) => (value ? value.replace(/Gi$/, 'GB') : '-')
},
{
title: intl.formatMessage({ id: 'common.table.status' }),
@@ -21,8 +21,8 @@ export default function useQueryStorageClass() {
>({
key: 'storageClass',
fetchList,
getLabel: (item) => item.metadata.name,
getValue: (item) => item.metadata.name
getLabel: (item) => item?.metadata.name,
getValue: (item) => item?.metadata.name
});
return {
@@ -7,7 +7,7 @@ import mooreLogo from '@/assets/logo/moore-logo.png';
import nvidiaLogo from '@/assets/logo/nvidia.png';
import theadLogoEN from '@/assets/logo/t-head-en.png';
import theadLogoZH from '@/assets/logo/t-head-zh.png';
import { GPUDriverMap } from '@/pages/resources/config/gpu-driver';
import { manfacturerValueMap } from '@/pages/resources/config/gpu-driver';
import {
AutoTooltip,
DropdownActions,
@@ -73,7 +73,7 @@ const InfoItem = styled.div`
color: var(--ant-color-text-quaternary);
}
.value {
color: var(--ant-color-text);
color: var(--ant-color-text-secondary);
}
`;
@@ -99,28 +99,28 @@ const TemplateCardItem: React.FC<TemplateCardProps> = ({ data, onSelect }) => {
const renderLogo = () => {
switch (data.manufacturer) {
case GPUDriverMap.NVIDIA:
case manfacturerValueMap.NVIDIA:
return <LogoImg src={nvidiaLogo} height={18} />;
case GPUDriverMap.AMD:
case manfacturerValueMap.AMD:
return (
<IconFont
type="icon-amd-logo"
style={{ fontSize: 36, color: 'var(--ant-color-text)' }}
/>
);
case GPUDriverMap.ASCEND:
case manfacturerValueMap.ASCEND:
return <LogoImg src={ascendLogo} height={24} />;
case GPUDriverMap.HYGON:
case manfacturerValueMap.HYGON:
return <LogoImg src={hyponPNG} height={18} />;
case GPUDriverMap.METAX:
case manfacturerValueMap.METAX:
return <LogoImg src={metaxLogo} height={20} />;
case GPUDriverMap.MOORE_THREADS:
case manfacturerValueMap.MOORE_THREADS:
return <LogoImg src={mooreLogo} height={22} />;
case GPUDriverMap.ILUVATAR:
case manfacturerValueMap.ILUVATAR:
return <LogoImg src={iluvatarWEBP} height={22} />;
case GPUDriverMap.CAMBRICON:
case manfacturerValueMap.CAMBRICON:
return <LogoImg src={CambriconPNG} height={22} />;
case GPUDriverMap.THEAD:
case manfacturerValueMap.THEAD:
return (
<LogoImg
src={intl?.locale === 'zh-CN' ? theadLogoZH : theadLogoEN}
@@ -18,11 +18,18 @@ import { FormData } from '../config/types';
import Env from './env';
import Ports from './ports';
interface BasicProps {
page?: 'template' | 'instance';
export interface BasicResourceMax {
cpu?: number | null;
memory?: number | null;
localStorage?: number | null;
}
const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
interface BasicProps {
page?: 'template' | 'instance';
onceMaxRequest?: BasicResourceMax;
}
const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
const { getRuleMessage } = useAppUtils();
const intl = useIntl();
@@ -30,11 +37,26 @@ const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
() =>
Object.values(GPUsConfigs).map((item) => ({
label: item.label,
value: item.value
value: item.gpuVendor
})),
[]
);
const renderMaxLabel = (
label: React.ReactNode,
max?: number | null
): React.ReactNode => {
if (max == null) return label;
return (
<Flex gap={4} align="center">
{label}
<span>
({intl.formatMessage({ id: 'common.max' }, { count: max })})
</span>
</Flex>
);
};
return (
<>
<div data-field="template"></div>
@@ -135,9 +157,11 @@ const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
})}
>
<InputNumber
label={intl.formatMessage({
id: 'gpuservice.template.containerDisk'
})}
label={renderMaxLabel(
intl.formatMessage({ id: 'gpuservice.template.containerDisk' }),
onceMaxRequest?.localStorage
)}
max={onceMaxRequest?.localStorage ?? undefined}
/>
</Form.Item>
</div>
@@ -149,7 +173,10 @@ const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
normalize={(value) => (value ? `${value}` : '')}
getValueProps={(value) => ({ value: value ? String(value) : '' })}
>
<InputNumber label="CPU" />
<InputNumber
label={renderMaxLabel('CPU', onceMaxRequest?.cpu)}
max={onceMaxRequest?.cpu ?? undefined}
/>
</Form.Item>
</div>
<div style={{ flex: 1 }}>
@@ -161,7 +188,11 @@ const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
})}
>
<InputNumber
label={intl.formatMessage({ id: 'gpuservice.template.memory' })}
label={renderMaxLabel(
intl.formatMessage({ id: 'gpuservice.template.memory' }),
onceMaxRequest?.memory
)}
max={onceMaxRequest?.memory ?? undefined}
/>
</Form.Item>
</div>
+37 -10
View File
@@ -24,63 +24,90 @@ export const ManufacturerMap = {
[GPUDriverMap.THEAD]: 'vendor.thead'
};
export const manfacturerValueMap = {
NVIDIA: 'nvidia',
AMD: 'amd',
ASCEND: 'ascend',
HYGON: 'hygon',
MOORE_THREADS: 'moorthreads',
ILUVATAR: 'iluvatar',
CAMBRICON: 'cambricon',
METAX: 'metax',
THEAD: 'thead'
};
export const GPUsConfigs: Record<
string,
{ label: string; value: string; runtime: string; driver: string }
{
label: string;
value: string;
runtime: string;
driver: string;
gpuVendor?: string;
}
> = {
[GPUDriverMap.NVIDIA]: {
label: 'NVIDIA', // this label is used in echo command, do not translate
value: GPUDriverMap.NVIDIA,
runtime: 'nvidia',
driver: 'nvidia-smi'
driver: 'nvidia-smi',
gpuVendor: 'nvidia'
},
[GPUDriverMap.AMD]: {
label: 'AMD',
value: GPUDriverMap.AMD,
runtime: 'amd',
driver: 'amd-smi static'
driver: 'amd-smi static',
gpuVendor: 'amd'
},
[GPUDriverMap.ASCEND]: {
label: 'Ascend',
value: GPUDriverMap.ASCEND,
runtime: 'ascend',
driver: 'npu-smi info'
driver: 'npu-smi info',
gpuVendor: 'ascend'
},
[GPUDriverMap.HYGON]: {
label: 'Hygon',
value: GPUDriverMap.HYGON,
runtime: '', // TODO: confirm runtime name
driver: 'hy-smi'
driver: 'hy-smi',
gpuVendor: 'hygon'
},
[GPUDriverMap.MOORE_THREADS]: {
label: 'Moore Threads',
value: GPUDriverMap.MOORE_THREADS,
runtime: 'mthreads',
driver: 'mthreads-gmi'
driver: 'mthreads-gmi',
gpuVendor: 'mthreads'
},
[GPUDriverMap.ILUVATAR]: {
label: 'Iluvatar',
value: GPUDriverMap.ILUVATAR,
runtime: 'iluvatar',
driver: 'ixsmi'
driver: 'ixsmi',
gpuVendor: 'iluvatar'
},
[GPUDriverMap.CAMBRICON]: {
label: 'Cambricon',
value: GPUDriverMap.CAMBRICON,
runtime: 'cambricon',
driver: 'cnmon info'
driver: 'cnmon info',
gpuVendor: 'cambricon'
},
[GPUDriverMap.METAX]: {
label: 'Metax',
value: GPUDriverMap.METAX,
runtime: 'metax',
driver: 'mx-smi'
driver: 'mx-smi',
gpuVendor: 'metax'
},
[GPUDriverMap.THEAD]: {
label: 'T-Head PPU',
value: GPUDriverMap.THEAD,
runtime: '', // TODO: confirm runtime name
driver: 'ppu-smi'
driver: 'ppu-smi',
gpuVendor: 'thead'
}
};