style: instance type remaing resourece
This commit is contained in:
@@ -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}'
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) ==========
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) ==========
|
||||
|
||||
@@ -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': 'Память',
|
||||
|
||||
@@ -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}'
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}'
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user