fix: style issues
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -116,7 +116,10 @@ export async function queryStorageClass(
|
||||
{
|
||||
method: 'GET',
|
||||
params: omitPathParams(params),
|
||||
cancelToken: options?.token
|
||||
cancelToken: options?.token,
|
||||
headers: {
|
||||
skipErrorHandler: 'true'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user