chore: cleanup file checkbox

This commit is contained in:
jialin
2025-03-26 11:59:49 +08:00
parent 93c9ffe109
commit 671fce2961
8 changed files with 166 additions and 60 deletions
+60 -25
View File
@@ -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>
); );
}); });
+1
View File
@@ -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;
+16 -5
View File
@@ -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();
} }
+2 -1
View File
@@ -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!'
}; };
+21 -11
View File
@@ -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 ==========
+2 -1
View File
@@ -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': '同时从磁盘删除文件!'
}; };
+10 -4
View File
@@ -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) {
+54 -13
View File
@@ -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>