chore: cleanup file checkbox
This commit is contained in:
@@ -1,45 +1,68 @@
|
|||||||
import useBodyScroll from '@/hooks/use-body-scroll';
|
import useBodyScroll from '@/hooks/use-body-scroll';
|
||||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Modal, Space, message, type ModalFuncProps } from 'antd';
|
import {
|
||||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Modal,
|
||||||
|
Space,
|
||||||
|
message,
|
||||||
|
type ModalFuncProps
|
||||||
|
} from 'antd';
|
||||||
|
import { FC, forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import Styles from './index.less';
|
import Styles from './index.less';
|
||||||
|
|
||||||
const DeleteModal = forwardRef((props, ref) => {
|
const CheckboxWrapper = styled.div`
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
.check-text {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--ant-color-warning);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface DeleteModalProps {
|
||||||
|
ref: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataOptions {
|
||||||
|
content: string;
|
||||||
|
selection?: boolean;
|
||||||
|
name?: string;
|
||||||
|
okText?: string;
|
||||||
|
cancelText?: string;
|
||||||
|
title?: string;
|
||||||
|
operation: string;
|
||||||
|
checkConfig?: {
|
||||||
|
checkText: string;
|
||||||
|
defautlChecked: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [config, setConfig] = useState<
|
const [checked, setChecked] = useState(false);
|
||||||
ModalFuncProps & {
|
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
|
||||||
content: string;
|
|
||||||
selection?: boolean;
|
|
||||||
name?: string;
|
|
||||||
okText?: string;
|
|
||||||
cancelText?: string;
|
|
||||||
title?: string;
|
|
||||||
operation: string;
|
|
||||||
}
|
|
||||||
>({} as any);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
show: (
|
show: (data: ModalFuncProps & DataOptions) => {
|
||||||
data: ModalFuncProps & {
|
|
||||||
content: string;
|
|
||||||
selection?: boolean;
|
|
||||||
name?: string;
|
|
||||||
title?: string;
|
|
||||||
cancelText?: string;
|
|
||||||
okText?: string;
|
|
||||||
operation: string;
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
saveScrollHeight();
|
saveScrollHeight();
|
||||||
setConfig(data);
|
setConfig(data);
|
||||||
|
setChecked(data.checkConfig?.defautlChecked || false);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
},
|
},
|
||||||
hide: () => {
|
hide: () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
restoreScrollHeight();
|
restoreScrollHeight();
|
||||||
|
},
|
||||||
|
configuration: {
|
||||||
|
checked: checked
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -111,6 +134,18 @@ const DeleteModal = forwardRef((props, ref) => {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
{config.checkConfig && (
|
||||||
|
<CheckboxWrapper>
|
||||||
|
<Checkbox
|
||||||
|
checked={checked}
|
||||||
|
onChange={(e) => setChecked(e.target.checked)}
|
||||||
|
>
|
||||||
|
<span className="check-text">
|
||||||
|
{intl.formatMessage({ id: config.checkConfig?.checkText })}
|
||||||
|
</span>
|
||||||
|
</Checkbox>
|
||||||
|
</CheckboxWrapper>
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ html {
|
|||||||
--color-scrollbar-track: var(--ant-color-fill-tertiary);
|
--color-scrollbar-track: var(--ant-color-fill-tertiary);
|
||||||
// --color-fill-1: #fff;
|
// --color-fill-1: #fff;
|
||||||
--ant-color-text: #000;
|
--ant-color-text: #000;
|
||||||
|
--color-bg-1: #f4f5f4;
|
||||||
--color-scroll-bg: #d9d9d9;
|
--color-scroll-bg: #d9d9d9;
|
||||||
--color-fill-2: #fff;
|
--color-fill-2: #fff;
|
||||||
--color-fill-3: #f3f6fa;
|
--color-fill-3: #f3f6fa;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
API?: string;
|
API?: string;
|
||||||
watch?: boolean;
|
watch?: boolean;
|
||||||
fetchAPI: (params: any) => Promise<Global.PageResponse<ListItem>>;
|
fetchAPI: (params: any) => Promise<Global.PageResponse<ListItem>>;
|
||||||
deleteAPI?: (id: number) => Promise<any>;
|
deleteAPI?: (id: number, params?: any) => Promise<any>;
|
||||||
contentForDelete?: string;
|
contentForDelete?: string;
|
||||||
}) {
|
}) {
|
||||||
const { fetchAPI, deleteAPI, contentForDelete, API, watch } = options;
|
const { fetchAPI, deleteAPI, contentForDelete, API, watch } = options;
|
||||||
@@ -139,27 +139,38 @@ export default function useTableFetch<ListItem>(options: {
|
|||||||
|
|
||||||
const handleNameChange = debounceUpdateFilter;
|
const handleNameChange = debounceUpdateFilter;
|
||||||
|
|
||||||
const handleDelete = (row: ListItem & { name: string; id: number }) => {
|
const handleDelete = (
|
||||||
|
row: ListItem & { name: string; id: number },
|
||||||
|
options?: any
|
||||||
|
) => {
|
||||||
modalRef.current.show({
|
modalRef.current.show({
|
||||||
content: contentForDelete,
|
content: contentForDelete,
|
||||||
operation: 'common.delete.single.confirm',
|
operation: 'common.delete.single.confirm',
|
||||||
name: row.name,
|
name: row.name,
|
||||||
|
...options,
|
||||||
async onOk() {
|
async onOk() {
|
||||||
console.log('OK');
|
console.log('OK');
|
||||||
await deleteAPI?.(row.id);
|
await deleteAPI?.(row.id, {
|
||||||
|
...modalRef.current?.configuration
|
||||||
|
});
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteBatch = () => {
|
const handleDeleteBatch = (options = {}) => {
|
||||||
modalRef.current.show({
|
modalRef.current.show({
|
||||||
content: contentForDelete,
|
content: contentForDelete,
|
||||||
operation: 'common.delete.confirm',
|
operation: 'common.delete.confirm',
|
||||||
selection: true,
|
selection: true,
|
||||||
|
...options,
|
||||||
async onOk() {
|
async onOk() {
|
||||||
if (!deleteAPI) return;
|
if (!deleteAPI) return;
|
||||||
await handleBatchRequest(rowSelection.selectedRowKeys, deleteAPI);
|
await handleBatchRequest(rowSelection.selectedRowKeys, (id) =>
|
||||||
|
deleteAPI(id, {
|
||||||
|
...modalRef.current?.configuration
|
||||||
|
})
|
||||||
|
);
|
||||||
rowSelection.clearSelections();
|
rowSelection.clearSelections();
|
||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,5 +68,6 @@ export default {
|
|||||||
'resources.modelfiles.storagePath.holder':
|
'resources.modelfiles.storagePath.holder':
|
||||||
'Waiting for download to complete...',
|
'Waiting for download to complete...',
|
||||||
'resources.filter.worker': 'Filter by Worker',
|
'resources.filter.worker': 'Filter by Worker',
|
||||||
'resources.filter.source': 'Filter by Source'
|
'resources.filter.source': 'Filter by Source',
|
||||||
|
'resources.modelfiles.delete.tips': 'Also delete the file from disk!'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,10 +13,14 @@ export default {
|
|||||||
'resources.form.enablePartialOffload': 'Разрешить оффлоуд на CPU',
|
'resources.form.enablePartialOffload': 'Разрешить оффлоуд на CPU',
|
||||||
'resources.form.placementStrategy': 'Стратегия размещения',
|
'resources.form.placementStrategy': 'Стратегия размещения',
|
||||||
'resources.form.workerSelector': 'Селектор воркеров',
|
'resources.form.workerSelector': 'Селектор воркеров',
|
||||||
'resources.form.enableDistributedInferenceAcrossWorkers': 'Разрешить распределённый инференс между воркерами',
|
'resources.form.enableDistributedInferenceAcrossWorkers':
|
||||||
'resources.form.spread.tips': 'Равномерно распределяет ресурсы между воркерами. Может увеличить фрагментацию ресурсов на отдельных воркерах.',
|
'Разрешить распределённый инференс между воркерами',
|
||||||
'resources.form.binpack.tips': 'Максимизирует утилизацию ресурсов, уменьшая фрагментацию на воркерах/GPU.',
|
'resources.form.spread.tips':
|
||||||
'resources.form.workerSelector.description': 'Система выбирает подходящие GPU/воркеры для развертывания моделей на основе меток.',
|
'Равномерно распределяет ресурсы между воркерами. Может увеличить фрагментацию ресурсов на отдельных воркерах.',
|
||||||
|
'resources.form.binpack.tips':
|
||||||
|
'Максимизирует утилизацию ресурсов, уменьшая фрагментацию на воркерах/GPU.',
|
||||||
|
'resources.form.workerSelector.description':
|
||||||
|
'Система выбирает подходящие GPU/воркеры для развертывания моделей на основе меток.',
|
||||||
'resources.table.ip': 'IP-адрес',
|
'resources.table.ip': 'IP-адрес',
|
||||||
'resources.table.cpu': 'CPU',
|
'resources.table.cpu': 'CPU',
|
||||||
'resources.table.memory': 'ОЗУ',
|
'resources.table.memory': 'ОЗУ',
|
||||||
@@ -39,12 +43,16 @@ export default {
|
|||||||
'resources.table.unified': 'Объединённая память',
|
'resources.table.unified': 'Объединённая память',
|
||||||
'resources.worker.add.step1': 'Получить токен',
|
'resources.worker.add.step1': 'Получить токен',
|
||||||
'resources.worker.add.step2': 'Зарегистрировать воркер',
|
'resources.worker.add.step2': 'Зарегистрировать воркер',
|
||||||
'resources.worker.add.step2.tips': 'Примечание: <span style="color: #000;font-weight: 600">mytoken</span> — токен из первого шага.',
|
'resources.worker.add.step2.tips':
|
||||||
'resources.worker.add.step3': 'После успешной регистрации обновите список воркеров.',
|
'Примечание: <span style="color: #000;font-weight: 600">mytoken</span> — токен из первого шага.',
|
||||||
|
'resources.worker.add.step3':
|
||||||
|
'После успешной регистрации обновите список воркеров.',
|
||||||
'resources.worker.container.supported': 'Только для Linux.',
|
'resources.worker.container.supported': 'Только для Linux.',
|
||||||
'resources.worker.current.version': 'Текущая версия: {version}',
|
'resources.worker.current.version': 'Текущая версия: {version}',
|
||||||
'resources.worker.driver.install': 'Установите необходимые драйверы и библиотеки перед установкой GPUStack.',
|
'resources.worker.driver.install':
|
||||||
'resources.worker.select.command': 'Выберите метку для генерации команды и скопируйте её.',
|
'Установите необходимые драйверы и библиотеки перед установкой GPUStack.',
|
||||||
|
'resources.worker.select.command':
|
||||||
|
'Выберите метку для генерации команды и скопируйте её.',
|
||||||
'resources.worker.script.install': 'Установка скриптом',
|
'resources.worker.script.install': 'Установка скриптом',
|
||||||
'resources.worker.container.install': 'Установка контейнером (только Linux)',
|
'resources.worker.container.install': 'Установка контейнером (только Linux)',
|
||||||
'resources.worker.cann.tips': `Укажите индексы GPU через <span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES=0,1,2,3</span> или <span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES=0-3</span>.`,
|
'resources.worker.cann.tips': `Укажите индексы GPU через <span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES=0,1,2,3</span> или <span style='color: #000;font-weight: 600'>ASCEND_VISIBLE_DEVICES=0-3</span>.`,
|
||||||
@@ -54,13 +62,15 @@ export default {
|
|||||||
'resources.modelfiles.size': 'Размер',
|
'resources.modelfiles.size': 'Размер',
|
||||||
'resources.modelfiles.selecttarget': 'Выбрать назначение',
|
'resources.modelfiles.selecttarget': 'Выбрать назначение',
|
||||||
'resources.modelfiles.form.localdir': 'Локальный каталог',
|
'resources.modelfiles.form.localdir': 'Локальный каталог',
|
||||||
'resources.modelfiles.form.localdir.tips': 'Каталог по умолчанию <span class="desc-block>/var/lib/gpustack/cache</span> или каталог, указанный через <span class="desc-block">--data-dir</span>.',
|
'resources.modelfiles.form.localdir.tips':
|
||||||
|
'Каталог по умолчанию <span class="desc-block>/var/lib/gpustack/cache</span> или каталог, указанный через <span class="desc-block">--data-dir</span>.',
|
||||||
'resources.modelfiles.retry.download': 'Повторить загрузку',
|
'resources.modelfiles.retry.download': 'Повторить загрузку',
|
||||||
'resources.modelfiles.storagePath.holder': 'Ожидание завершения загрузки...',
|
'resources.modelfiles.storagePath.holder': 'Ожидание завершения загрузки...',
|
||||||
'resources.filter.worker': 'Фильтровать по узлу',
|
'resources.filter.worker': 'Фильтровать по узлу',
|
||||||
'resources.filter.source': 'Фильтровать по источнику'
|
'resources.filter.source': 'Фильтровать по источнику',
|
||||||
|
'resources.modelfiles.delete.tips': 'Also delete the file from disk!'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'resources.modelfiles.delete.tips'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -66,5 +66,6 @@ export default {
|
|||||||
'resources.modelfiles.retry.download': '重新下载',
|
'resources.modelfiles.retry.download': '重新下载',
|
||||||
'resources.modelfiles.storagePath.holder': '等待下载完成...',
|
'resources.modelfiles.storagePath.holder': '等待下载完成...',
|
||||||
'resources.filter.worker': '按 worker 筛选',
|
'resources.filter.worker': '按 worker 筛选',
|
||||||
'resources.filter.source': '按来源筛选'
|
'resources.filter.source': '按来源筛选',
|
||||||
|
'resources.modelfiles.delete.tips': '同时从磁盘删除文件!'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,10 +45,16 @@ export async function queryModelFilesList(params: Global.SearchParams) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteModelFile(id: string | number) {
|
export async function deleteModelFile(
|
||||||
return request<Global.PageResponse<ModelFile>>(`${MODEL_FILES_API}/${id}`, {
|
id: string | number,
|
||||||
method: 'DELETE'
|
params: { checked: boolean }
|
||||||
});
|
) {
|
||||||
|
return request<Global.PageResponse<ModelFile>>(
|
||||||
|
`${MODEL_FILES_API}/${id}?cleanup=${params.checked}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE'
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateModelFile(id: string | number, data: any) {
|
export async function updateModelFile(id: string | number, data: any) {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import dayjs from 'dayjs';
|
|||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
useGenerateFormEditInitialValues,
|
useGenerateFormEditInitialValues,
|
||||||
useGenerateModelFileOptions
|
useGenerateModelFileOptions
|
||||||
@@ -69,6 +70,26 @@ import {
|
|||||||
|
|
||||||
const filterPattern = /^(.*?)(?:-\d+-of-\d+)?(\.gguf)?$/;
|
const filterPattern = /^(.*?)(?:-\d+-of-\d+)?(\.gguf)?$/;
|
||||||
|
|
||||||
|
const PathWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
.btn-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--color-bg-1);
|
||||||
|
right: 0;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.btn-wrapper {
|
||||||
|
display: flex;
|
||||||
|
background: var(--ant-table-row-hover-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const getWorkerName = (
|
const getWorkerName = (
|
||||||
id: number,
|
id: number,
|
||||||
workersList: Global.BaseOption<number>[]
|
workersList: Global.BaseOption<number>[]
|
||||||
@@ -76,6 +97,7 @@ const getWorkerName = (
|
|||||||
const worker = workersList.find((item) => item.value === id);
|
const worker = workersList.find((item) => item.value === id);
|
||||||
return worker?.label || '';
|
return worker?.label || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const InstanceStatusTag = (props: { data: ListItem }) => {
|
const InstanceStatusTag = (props: { data: ListItem }) => {
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
if (!data.state) {
|
if (!data.state) {
|
||||||
@@ -272,10 +294,18 @@ const ModelFiles = () => {
|
|||||||
const handleSelect = async (val: any, record: ListItem) => {
|
const handleSelect = async (val: any, record: ListItem) => {
|
||||||
try {
|
try {
|
||||||
if (val === 'delete') {
|
if (val === 'delete') {
|
||||||
handleDelete({
|
handleDelete(
|
||||||
...record,
|
{
|
||||||
name: record.local_path
|
...record,
|
||||||
});
|
name: record.local_path
|
||||||
|
},
|
||||||
|
{
|
||||||
|
checkConfig: {
|
||||||
|
checkText: 'resources.modelfiles.delete.tips',
|
||||||
|
defautlChecked: record.source !== modelSourceMap.local_path_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
} else if (val === 'retry') {
|
} else if (val === 'retry') {
|
||||||
await retryDownloadModelFile(record.id);
|
await retryDownloadModelFile(record.id);
|
||||||
showSuccess();
|
showSuccess();
|
||||||
@@ -357,6 +387,15 @@ const ModelFiles = () => {
|
|||||||
restoreScrollHeight();
|
restoreScrollHeight();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteByBatch = () => {
|
||||||
|
handleDeleteBatch({
|
||||||
|
checkConfig: {
|
||||||
|
checkText: 'resources.modelfiles.delete.tips',
|
||||||
|
defautlChecked: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleCreateModel = async (data: any) => {
|
const handleCreateModel = async (data: any) => {
|
||||||
try {
|
try {
|
||||||
const result = getSourceRepoConfigValue(openDeployModal.source, data);
|
const result = getSourceRepoConfigValue(openDeployModal.source, data);
|
||||||
@@ -430,17 +469,19 @@ const ModelFiles = () => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
record.resolved_paths?.length > 0 && (
|
record.resolved_paths?.length > 0 && (
|
||||||
<span className="flex-center">
|
<PathWrapper>
|
||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost>
|
||||||
<span>{record.resolved_paths?.[0]}</span>
|
<span>{record.resolved_paths?.[0]}</span>
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
<CopyButton
|
<span className="btn-wrapper">
|
||||||
text={record.resolved_paths?.[0]}
|
<CopyButton
|
||||||
type="link"
|
text={record.resolved_paths?.[0]}
|
||||||
btnStyle={{ width: 18, paddingInline: 2 }}
|
type="link"
|
||||||
></CopyButton>
|
btnStyle={{ width: 18, paddingInline: 2 }}
|
||||||
{renderParts(record)}
|
></CopyButton>
|
||||||
</span>
|
{renderParts(record)}
|
||||||
|
</span>
|
||||||
|
</PathWrapper>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -534,7 +575,7 @@ const ModelFiles = () => {
|
|||||||
<Button
|
<Button
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
danger
|
danger
|
||||||
onClick={handleDeleteBatch}
|
onClick={handleDeleteByBatch}
|
||||||
disabled={!rowSelection.selectedRowKeys.length}
|
disabled={!rowSelection.selectedRowKeys.length}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
Reference in New Issue
Block a user