chore: cleanup file checkbox

main
jialin 1 year ago
parent 93c9ffe109
commit 671fce2961

@ -1,45 +1,68 @@
import useBodyScroll from '@/hooks/use-body-scroll';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Modal, Space, message, type ModalFuncProps } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
import {
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';
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 { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [visible, setVisible] = useState(false);
const [config, setConfig] = useState<
ModalFuncProps & {
content: string;
selection?: boolean;
name?: string;
okText?: string;
cancelText?: string;
title?: string;
operation: string;
}
>({} as any);
const [checked, setChecked] = useState(false);
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
useImperativeHandle(ref, () => ({
show: (
data: ModalFuncProps & {
content: string;
selection?: boolean;
name?: string;
title?: string;
cancelText?: string;
okText?: string;
operation: string;
}
) => {
show: (data: ModalFuncProps & DataOptions) => {
saveScrollHeight();
setConfig(data);
setChecked(data.checkConfig?.defautlChecked || false);
setVisible(true);
},
hide: () => {
setVisible(false);
restoreScrollHeight();
},
configuration: {
checked: checked
}
}));
@ -111,6 +134,18 @@ const DeleteModal = forwardRef((props, ref) => {
)
}}
></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>
);
});

@ -24,6 +24,7 @@ html {
--color-scrollbar-track: var(--ant-color-fill-tertiary);
// --color-fill-1: #fff;
--ant-color-text: #000;
--color-bg-1: #f4f5f4;
--color-scroll-bg: #d9d9d9;
--color-fill-2: #fff;
--color-fill-3: #f3f6fa;

@ -11,7 +11,7 @@ export default function useTableFetch<ListItem>(options: {
API?: string;
watch?: boolean;
fetchAPI: (params: any) => Promise<Global.PageResponse<ListItem>>;
deleteAPI?: (id: number) => Promise<any>;
deleteAPI?: (id: number, params?: any) => Promise<any>;
contentForDelete?: string;
}) {
const { fetchAPI, deleteAPI, contentForDelete, API, watch } = options;
@ -139,27 +139,38 @@ export default function useTableFetch<ListItem>(options: {
const handleNameChange = debounceUpdateFilter;
const handleDelete = (row: ListItem & { name: string; id: number }) => {
const handleDelete = (
row: ListItem & { name: string; id: number },
options?: any
) => {
modalRef.current.show({
content: contentForDelete,
operation: 'common.delete.single.confirm',
name: row.name,
...options,
async onOk() {
console.log('OK');
await deleteAPI?.(row.id);
await deleteAPI?.(row.id, {
...modalRef.current?.configuration
});
fetchData();
}
});
};
const handleDeleteBatch = () => {
const handleDeleteBatch = (options = {}) => {
modalRef.current.show({
content: contentForDelete,
operation: 'common.delete.confirm',
selection: true,
...options,
async onOk() {
if (!deleteAPI) return;
await handleBatchRequest(rowSelection.selectedRowKeys, deleteAPI);
await handleBatchRequest(rowSelection.selectedRowKeys, (id) =>
deleteAPI(id, {
...modalRef.current?.configuration
})
);
rowSelection.clearSelections();
fetchData();
}

@ -68,5 +68,6 @@ export default {
'resources.modelfiles.storagePath.holder':
'Waiting for download to complete...',
'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.placementStrategy': 'Стратегия размещения',
'resources.form.workerSelector': 'Селектор воркеров',
'resources.form.enableDistributedInferenceAcrossWorkers': 'Разрешить распределённый инференс между воркерами',
'resources.form.spread.tips': 'Равномерно распределяет ресурсы между воркерами. Может увеличить фрагментацию ресурсов на отдельных воркерах.',
'resources.form.binpack.tips': 'Максимизирует утилизацию ресурсов, уменьшая фрагментацию на воркерах/GPU.',
'resources.form.workerSelector.description': 'Система выбирает подходящие GPU/воркеры для развертывания моделей на основе меток.',
'resources.form.enableDistributedInferenceAcrossWorkers':
'Разрешить распределённый инференс между воркерами',
'resources.form.spread.tips':
'Равномерно распределяет ресурсы между воркерами. Может увеличить фрагментацию ресурсов на отдельных воркерах.',
'resources.form.binpack.tips':
'Максимизирует утилизацию ресурсов, уменьшая фрагментацию на воркерах/GPU.',
'resources.form.workerSelector.description':
'Система выбирает подходящие GPU/воркеры для развертывания моделей на основе меток.',
'resources.table.ip': 'IP-адрес',
'resources.table.cpu': 'CPU',
'resources.table.memory': 'ОЗУ',
@ -39,12 +43,16 @@ export default {
'resources.table.unified': 'Объединённая память',
'resources.worker.add.step1': 'Получить токен',
'resources.worker.add.step2': 'Зарегистрировать воркер',
'resources.worker.add.step2.tips': 'Примечание: <span style="color: #000;font-weight: 600">mytoken</span> — токен из первого шага.',
'resources.worker.add.step3': 'После успешной регистрации обновите список воркеров.',
'resources.worker.add.step2.tips':
'Примечание: <span style="color: #000;font-weight: 600">mytoken</span> — токен из первого шага.',
'resources.worker.add.step3':
'После успешной регистрации обновите список воркеров.',
'resources.worker.container.supported': 'Только для Linux.',
'resources.worker.current.version': 'Текущая версия: {version}',
'resources.worker.driver.install': 'Установите необходимые драйверы и библиотеки перед установкой GPUStack.',
'resources.worker.select.command': 'Выберите метку для генерации команды и скопируйте её.',
'resources.worker.driver.install':
'Установите необходимые драйверы и библиотеки перед установкой GPUStack.',
'resources.worker.select.command':
'Выберите метку для генерации команды и скопируйте её.',
'resources.worker.script.install': 'Установка скриптом',
'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>.`,
@ -54,13 +62,15 @@ export default {
'resources.modelfiles.size': 'Размер',
'resources.modelfiles.selecttarget': 'Выбрать назначение',
'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.storagePath.holder': 'Ожидание завершения загрузки...',
'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) ==========
// 1. 'resources.modelfiles.delete.tips'
// ========== End of To-Do List ==========

@ -66,5 +66,6 @@ export default {
'resources.modelfiles.retry.download': '重新下载',
'resources.modelfiles.storagePath.holder': '等待下载完成...',
'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) {
return request<Global.PageResponse<ModelFile>>(`${MODEL_FILES_API}/${id}`, {
method: 'DELETE'
});
export async function deleteModelFile(
id: string | number,
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) {

@ -44,6 +44,7 @@ import dayjs from 'dayjs';
import { useAtom } from 'jotai';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import styled from 'styled-components';
import {
useGenerateFormEditInitialValues,
useGenerateModelFileOptions
@ -69,6 +70,26 @@ import {
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 = (
id: number,
workersList: Global.BaseOption<number>[]
@ -76,6 +97,7 @@ const getWorkerName = (
const worker = workersList.find((item) => item.value === id);
return worker?.label || '';
};
const InstanceStatusTag = (props: { data: ListItem }) => {
const { data } = props;
if (!data.state) {
@ -272,10 +294,18 @@ const ModelFiles = () => {
const handleSelect = async (val: any, record: ListItem) => {
try {
if (val === 'delete') {
handleDelete({
...record,
name: record.local_path
});
handleDelete(
{
...record,
name: record.local_path
},
{
checkConfig: {
checkText: 'resources.modelfiles.delete.tips',
defautlChecked: record.source !== modelSourceMap.local_path_value
}
}
);
} else if (val === 'retry') {
await retryDownloadModelFile(record.id);
showSuccess();
@ -357,6 +387,15 @@ const ModelFiles = () => {
restoreScrollHeight();
};
const handleDeleteByBatch = () => {
handleDeleteBatch({
checkConfig: {
checkText: 'resources.modelfiles.delete.tips',
defautlChecked: false
}
});
};
const handleCreateModel = async (data: any) => {
try {
const result = getSourceRepoConfigValue(openDeployModal.source, data);
@ -430,17 +469,19 @@ const ModelFiles = () => {
}
return (
record.resolved_paths?.length > 0 && (
<span className="flex-center">
<PathWrapper>
<AutoTooltip ghost>
<span>{record.resolved_paths?.[0]}</span>
</AutoTooltip>
<CopyButton
text={record.resolved_paths?.[0]}
type="link"
btnStyle={{ width: 18, paddingInline: 2 }}
></CopyButton>
{renderParts(record)}
</span>
<span className="btn-wrapper">
<CopyButton
text={record.resolved_paths?.[0]}
type="link"
btnStyle={{ width: 18, paddingInline: 2 }}
></CopyButton>
{renderParts(record)}
</span>
</PathWrapper>
)
);
}
@ -534,7 +575,7 @@ const ModelFiles = () => {
<Button
icon={<DeleteOutlined />}
danger
onClick={handleDeleteBatch}
onClick={handleDeleteByBatch}
disabled={!rowSelection.selectedRowKeys.length}
>
<span>

Loading…
Cancel
Save