mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-05-06 18:22:44 +08:00
feat: enhance adapter usage tracking with new interface and display capacity usage in AdaptersPage
This commit is contained in:
@@ -10,6 +10,20 @@ export interface AdapterItem {
|
||||
sub_path?: string | null;
|
||||
}
|
||||
|
||||
export interface AdapterUsage {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
path: string;
|
||||
supported: boolean;
|
||||
used_bytes?: number | null;
|
||||
total_bytes?: number | null;
|
||||
free_bytes?: number | null;
|
||||
source?: string | null;
|
||||
scope?: string | null;
|
||||
reason?: string | null;
|
||||
}
|
||||
|
||||
export interface AdapterTypeField {
|
||||
key: string;
|
||||
label: string;
|
||||
@@ -25,20 +39,6 @@ export interface AdapterTypeMeta {
|
||||
config_schema: AdapterTypeField[];
|
||||
}
|
||||
|
||||
export interface AdapterUsage {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
path: string;
|
||||
supported: boolean;
|
||||
used_bytes?: number | null;
|
||||
total_bytes?: number | null;
|
||||
free_bytes?: number | null;
|
||||
source?: string | null;
|
||||
scope?: string | null;
|
||||
reason?: string | null;
|
||||
}
|
||||
|
||||
export const adaptersApi = {
|
||||
list: () => request<AdapterItem[]>('/adapters'),
|
||||
create: (payload: Omit<AdapterItem, 'id'>) => request<AdapterItem>('/adapters', { method: 'POST', json: payload }),
|
||||
|
||||
@@ -71,7 +71,7 @@ async function request<T = any>(url: string, options: RequestOptions = {}): Prom
|
||||
}
|
||||
|
||||
export { vfsApi, type VfsEntry, type DirListing } from './vfs';
|
||||
export { adaptersApi, type AdapterItem, type AdapterTypeField, type AdapterTypeMeta } from './adapters';
|
||||
export { adaptersApi, type AdapterItem, type AdapterTypeField, type AdapterTypeMeta, type AdapterUsage } from './adapters';
|
||||
export { shareApi, type ShareInfo, type ShareInfoWithPassword } from './share';
|
||||
export { offlineDownloadsApi, type OfflineDownloadTask, type OfflineDownloadCreate, type TaskProgress } from './offlineDownloads';
|
||||
export default request;
|
||||
|
||||
@@ -514,6 +514,8 @@
|
||||
"Unique name": "Unique name",
|
||||
"Select adapter type": "Select adapter type",
|
||||
"/ or /drive": "/ or /drive",
|
||||
"Used Capacity": "Used Capacity",
|
||||
"Capacity Usage": "Capacity Usage",
|
||||
"Adapter Config": "Adapter Config",
|
||||
"adapter.type.local": "Local Filesystem",
|
||||
"adapter.type.foxel": "Foxel Node",
|
||||
|
||||
@@ -513,6 +513,8 @@
|
||||
"Unique name": "唯一名称",
|
||||
"Select adapter type": "选择适配器类型",
|
||||
"/ or /drive": "/或/drive",
|
||||
"Used Capacity": "已使用容量",
|
||||
"Capacity Usage": "容量使用",
|
||||
"Adapter Config": "适配器配置",
|
||||
"adapter.type.local": "本地文件系统",
|
||||
"adapter.type.foxel": "Foxel 节点",
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
import { memo, useState, useEffect, useCallback } from 'react';
|
||||
import { Table, Button, Space, Drawer, Form, Input, Switch, message, Typography, Popconfirm, Select } from 'antd';
|
||||
import PageCard from '../components/PageCard';
|
||||
import { adaptersApi, type AdapterItem, type AdapterTypeMeta } from '../api/client';
|
||||
import { adaptersApi, type AdapterItem, type AdapterTypeMeta, type AdapterUsage } from '../api/client';
|
||||
import { useI18n } from '../i18n';
|
||||
|
||||
const formatBytes = (bytes?: number | null) => {
|
||||
if (bytes === null || bytes === undefined) return '-';
|
||||
if (bytes === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
||||
const value = bytes / (1024 ** index);
|
||||
return `${value.toFixed(value >= 10 || index === 0 ? 0 : 1)} ${units[index]}`;
|
||||
};
|
||||
|
||||
const formatUsage = (usage?: AdapterUsage) => {
|
||||
if (!usage?.supported || usage.used_bytes === null || usage.used_bytes === undefined) return '-';
|
||||
const used = formatBytes(usage.used_bytes);
|
||||
if (usage.total_bytes === null || usage.total_bytes === undefined) return used;
|
||||
return `${used} / ${formatBytes(usage.total_bytes)}`;
|
||||
};
|
||||
|
||||
const AdaptersPage = memo(function AdaptersPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<AdapterItem[]>([]);
|
||||
const [usageMap, setUsageMap] = useState<Record<number, AdapterUsage>>({});
|
||||
const [open, setOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<AdapterItem | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
@@ -16,12 +33,14 @@ const AdaptersPage = memo(function AdaptersPage() {
|
||||
const fetchList = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [list, types] = await Promise.all([
|
||||
const [list, types, usages] = await Promise.all([
|
||||
adaptersApi.list(),
|
||||
adaptersApi.available()
|
||||
adaptersApi.available(),
|
||||
adaptersApi.usage()
|
||||
]);
|
||||
setData(list);
|
||||
setAvailableTypes(types);
|
||||
setUsageMap(Object.fromEntries(usages.map(item => [item.id, item])));
|
||||
} catch (e: any) {
|
||||
message.error(e.message || t('Load failed'));
|
||||
} finally {
|
||||
@@ -142,6 +161,13 @@ const AdaptersPage = memo(function AdaptersPage() {
|
||||
{ title: t('Type'), dataIndex: 'type', width: 140, render: (value: string) => renderTypeLabel(value) },
|
||||
{ title: t('Mount Path'), dataIndex: 'path', width: 140, render: (v: string) => v || '-' },
|
||||
{ title: t('Sub Path'), dataIndex: 'sub_path', width: 140, render: (v: string) => v || '-' },
|
||||
{
|
||||
title: t('Capacity Usage'),
|
||||
width: 180,
|
||||
render: (_: any, rec: AdapterItem) => {
|
||||
return formatUsage(usageMap[rec.id]);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('Enabled'),
|
||||
dataIndex: 'enabled',
|
||||
|
||||
Reference in New Issue
Block a user