style: instance type remaing resourece

This commit is contained in:
jialin
2026-05-25 22:42:02 +08:00
committed by jialin
parent 858e0dae09
commit a0b408e8cb
16 changed files with 97 additions and 63 deletions
+1 -1
View File
@@ -283,5 +283,5 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.',
'common.max': 'Max {count}'
'common.remaining': 'Remaining {count}'
};
+3 -1
View File
@@ -55,7 +55,9 @@ export default {
'gpuservice.instance.gpuCount': 'GPU Count',
'gpuservice.instance.gpuCount.required': 'Please enter the GPU count',
'gpuservice.instance.gpuCount.max':
'The current instance type supports at most {count} GPU(s)',
'Please select at most {count} GPU card(s)',
'gpuservice.instance.gpuCount.min':
'Please select at least {count} GPU card(s)',
'gpuservice.instance.stock': 'Stock',
'gpuservice.instance.sliced': 'Sliced',
'gpuservice.instance.memory': 'Memory',
+1 -1
View File
@@ -283,7 +283,7 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.',
'common.max': '最大 {count}'
'common.remaining': '残り {count}'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
+3 -1
View File
@@ -53,7 +53,9 @@ export default {
'gpuservice.instance.gpuCount': 'GPU 数',
'gpuservice.instance.gpuCount.required': 'GPU 数を入力してください',
'gpuservice.instance.gpuCount.max':
'現在のインスタンスタイプは最大 {count} の GPU をサポートします',
'最大 {count} の GPU カードを選択してください',
'gpuservice.instance.gpuCount.min':
'少なくとも {count} 枚の GPU カードを選択してください',
'gpuservice.instance.stock': '在庫',
'gpuservice.instance.sliced': '分割',
'gpuservice.instance.memory': 'Memory',
+1 -1
View File
@@ -282,7 +282,7 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.',
'common.max': 'Макс. {count}'
'common.remaining': 'Остаток {count}'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
+2 -2
View File
@@ -55,8 +55,8 @@ export default {
'gpuservice.instance.type.required': 'Выберите тип экземпляра',
'gpuservice.instance.gpuCount': 'Количество GPU',
'gpuservice.instance.gpuCount.required': 'Введите количество GPU',
'gpuservice.instance.gpuCount.max':
'Текущий тип экземпляра поддерживает максимум {count} GPU',
'gpuservice.instance.gpuCount.max': 'Выберите максимум {count} GPU-карт',
'gpuservice.instance.gpuCount.min': 'Выберите минимум {count} GPU-карт',
'gpuservice.instance.stock': 'Остаток',
'gpuservice.instance.sliced': 'Разделено',
'gpuservice.instance.memory': 'Память',
+1 -1
View File
@@ -286,5 +286,5 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.',
'common.max': 'Maks. {count}'
'common.remaining': 'Kalan {count}'
};
+2 -2
View File
@@ -54,8 +54,8 @@ export default {
'gpuservice.instance.type.required': 'Lütfen bir örnek türü seçin',
'gpuservice.instance.gpuCount': 'GPU Sayısı',
'gpuservice.instance.gpuCount.required': 'Lütfen GPU sayısını girin',
'gpuservice.instance.gpuCount.max':
'Mevcut örnek türü en fazla {count} GPU destekler',
'gpuservice.instance.gpuCount.max': 'En fazla {count} GPU kartı seçin',
'gpuservice.instance.gpuCount.min': 'En az {count} GPU kartı seçin',
'gpuservice.instance.stock': 'Stok',
'gpuservice.instance.sliced': 'Bölünmüş',
'gpuservice.instance.memory': 'Bellek',
+1 -1
View File
@@ -275,5 +275,5 @@ export default {
'common.file.format.limit': '文件格式不正确,仅支持{formats}。',
'common.image.limit.width': '图片宽度须为{width}。',
'common.image.limit.height': '图片高度须为{height}。',
'common.max': '最大 {count}'
'common.remaining': '剩余 {count}'
};
+2 -1
View File
@@ -50,7 +50,8 @@ export default {
'gpuservice.instance.type.required': '请选择实例类型',
'gpuservice.instance.gpuCount': 'GPU 数量',
'gpuservice.instance.gpuCount.required': '请输入 GPU 数量',
'gpuservice.instance.gpuCount.max': '当前实例类型最多支持 {count} 个 GPU',
'gpuservice.instance.gpuCount.max': '最多选择 {count} 张卡',
'gpuservice.instance.gpuCount.min': '至少选择 {count} 张卡',
'gpuservice.instance.stock': '库存',
'gpuservice.instance.sliced': '切分',
'gpuservice.instance.memory': '显存',
@@ -9,6 +9,9 @@ import {
} from '../config';
import { InstanceTypeItem as InstanceTypeItemModel } from '../config/types';
const toDisplayUnit = (value?: string) =>
value ? value.replace(/Gi$/, 'GB').replace(/Ti$/, 'TB') : value;
const Title = styled.div`
display: flex;
align-items: center;
@@ -112,7 +115,9 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
<span>
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}
</span>
<span>{convertKiToGi(item.spec?.memory) ?? '-'}</span>
<span>
{toDisplayUnit(convertKiToGi(item.spec?.memory)) ?? '-'}
</span>
</Flex>
</span>
{item.spec?.sliced && (
@@ -157,7 +162,7 @@ const InstanceTypeItem: React.FC<InstanceTypeItemProps> = ({
<span>
{intl.formatMessage({ id: 'gpuservice.instance.ram' })}
</span>
<span>{item.status?.ram?.capacity ?? '-'}</span>
<span>{toDisplayUnit(item.status?.ram?.capacity) ?? '-'}</span>
</Flex>
</span>
<span className="dot"></span>
@@ -1,7 +1,8 @@
import { LogsViewer } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { Modal } from 'antd';
import React, { useCallback, useEffect } from 'react';
import React, { useEffect, useMemo } from 'react';
type ViewModalProps = {
open: boolean;
@@ -12,16 +13,17 @@ type ViewModalProps = {
};
const ViewLogsModal: React.FC<ViewModalProps> = (props) => {
console.log('ViewLogsModal props:', props);
const intl = useIntl();
const { open, onCancel, url } = props || {};
const { open, onCancel, url, tail } = props || {};
const logsViewerRef = React.useRef<any>(null);
const requestRef = React.useRef<any>(null);
const contentRef = React.useRef<any>(null);
const handleCancel = useCallback(() => {
const handleCancel = useMemoizedFn(() => {
logsViewerRef.current?.abort();
onCancel();
}, [onCancel]);
});
useEffect(() => {
const handleKeyDown = (e: any) => {
@@ -59,6 +61,15 @@ const ViewLogsModal: React.FC<ViewModalProps> = (props) => {
};
}, [url, open]);
const params = useMemo(() => {
return {
watchable: false,
watch: false,
tailLines: 1000,
follow: true
};
}, []);
return (
<Modal
title={
@@ -91,15 +102,10 @@ const ViewLogsModal: React.FC<ViewModalProps> = (props) => {
ref={logsViewerRef}
diffHeight={78}
url={url}
tail={undefined}
tail={1000}
enableScorllLoad={true}
isDownloading={false}
params={{
watchable: false,
watch: false,
tailLines: 1000,
follow: true
}}
params={params}
></LogsViewer>
</div>
</Modal>
@@ -11,7 +11,6 @@ import {
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import _ from 'lodash';
import {
forwardRef,
useEffect,
@@ -51,7 +50,7 @@ const parseQuantityToNumber = (value?: string): number | null => {
const match = /^(-?\d+(?:\.\d+)?)/.exec(String(value));
if (!match) return null;
const num = Number(match[1]);
return Number.isFinite(num) && num > 0 ? _.floor(num, 1) : null;
return Number.isFinite(num) && num > 0 ? num : null;
};
const TABKeysMap = {
@@ -224,14 +223,13 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
}, [action, currentData, form, open, namespace]);
const handleFinish = async (values: InstanceFormValues) => {
console.log('values=========', values);
// await onFinish({
// ...values,
// spec: {
// ...values.spec,
// sshPublicKey: { name: sshKeyData?.name }
// }
// });
await onFinish({
...values,
spec: {
...values.spec,
sshPublicKey: { name: sshKeyData?.name }
}
});
};
useImperativeHandle(ref, () => ({
@@ -116,7 +116,7 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
<InstanceTypePicker dataList={dataList} value={typeName} />
</Form.Item>
</FieldBlock>
{selectedInstanceType?.spec?.acceleratable && (
{!selectedInstanceType?.spec?.acceleratable && (
<Form.Item
name={['spec', 'resources', 'accelerator']}
rules={[
@@ -138,6 +138,15 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
)
);
}
if (value < 1) {
return Promise.reject(
new Error(
intl.formatMessage({
id: 'gpuservice.instance.gpuCount.min'
})
)
);
}
return Promise.resolve();
}
}
@@ -6,11 +6,11 @@ import {
StatusTag
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Button, Flex } from 'antd';
import { Button } from 'antd';
import type { ColumnsType } from 'antd/lib/table';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useMemo } from 'react';
import { Fragment, useMemo } from 'react';
import { InstanceStatusLabelMap, rowActionList, status } from '../config';
import { ListItem } from '../config/types';
@@ -35,6 +35,7 @@ const getConnectEntries = (record: ListItem): ConnectEntry[] => {
const ip = record.status?.hostIPs?.[0]?.ip;
const ports = record.status?.ports || [];
const hasSshKey = !!record.spec?.sshPublicKey?.name;
const configPorts = record.spec?.ports || [];
if (!ip) {
return [];
@@ -54,7 +55,9 @@ const getConnectEntries = (record: ListItem): ConnectEntry[] => {
}
: {
type: 'http',
name: p.name || 'HTTP',
name:
configPorts.find((port) => port.port === p.port)?.name ||
_.toUpper(p.protocol),
key: `http-${p.nodePort}`,
port: p.port,
protocol: _.toUpper(p.protocol),
@@ -100,16 +103,32 @@ const useInstancesColumns = ({
}
return (
<Flex gap={6} vertical>
{entries.map((entry) =>
entry.type === 'ssh' ? (
<Flex key={entry.key} align="center" style={{ height: 22 }}>
<span
className="text-tertiary"
style={{ fontSize: 12, width: 34, flexShrink: 0 }}
>
SSH
</span>
<div
style={{
display: 'grid',
gridTemplateColumns: 'max-content max-content',
rowGap: 6,
columnGap: 8,
alignItems: 'center'
}}
>
{entries.map((entry) => (
<Fragment key={entry.key}>
<span
className="text-tertiary"
style={{
fontSize: 12,
maxWidth: 80,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
>
{entry.type === 'ssh'
? 'SSH'
: entry.name || entry.protocol}
</span>
{entry.type === 'ssh' ? (
<CopyButton
text={entry.command}
type="link"
@@ -127,15 +146,7 @@ const useInstancesColumns = ({
fontSize: 12
}}
/>
</Flex>
) : (
<Flex key={entry.key} align="center" style={{ height: 22 }}>
<span
className="text-tertiary"
style={{ fontSize: 12, width: 34, flexShrink: 0 }}
>
{entry.name || entry.protocol}
</span>
) : (
<Button
type="link"
size="small"
@@ -155,10 +166,10 @@ const useInstancesColumns = ({
<span>{entry.port}</span>
<ExportOutlined style={{ fontSize: 10, marginLeft: 4 }} />
</Button>
</Flex>
)
)}
</Flex>
)}
</Fragment>
))}
</div>
);
}
},
@@ -57,7 +57,7 @@ const Basic: React.FC<BasicProps> = ({
<Flex gap={4} align="center">
{label}
<span>
({intl.formatMessage({ id: 'common.max' }, { count: max })})
({intl.formatMessage({ id: 'common.remaining' }, { count: max })})
</span>
</Flex>
);