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