mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-05 23:57:19 +08:00
feat: enhance adapter usage tracking with new interface and display capacity usage in AdaptersPage
This commit is contained in:
+14
-14
@@ -10,6 +10,20 @@ export interface AdapterItem {
|
|||||||
sub_path?: string | null;
|
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 {
|
export interface AdapterTypeField {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -25,20 +39,6 @@ export interface AdapterTypeMeta {
|
|||||||
config_schema: AdapterTypeField[];
|
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 = {
|
export const adaptersApi = {
|
||||||
list: () => request<AdapterItem[]>('/adapters'),
|
list: () => request<AdapterItem[]>('/adapters'),
|
||||||
create: (payload: Omit<AdapterItem, 'id'>) => request<AdapterItem>('/adapters', { method: 'POST', json: payload }),
|
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 { 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 { shareApi, type ShareInfo, type ShareInfoWithPassword } from './share';
|
||||||
export { offlineDownloadsApi, type OfflineDownloadTask, type OfflineDownloadCreate, type TaskProgress } from './offlineDownloads';
|
export { offlineDownloadsApi, type OfflineDownloadTask, type OfflineDownloadCreate, type TaskProgress } from './offlineDownloads';
|
||||||
export default request;
|
export default request;
|
||||||
|
|||||||
@@ -514,6 +514,8 @@
|
|||||||
"Unique name": "Unique name",
|
"Unique name": "Unique name",
|
||||||
"Select adapter type": "Select adapter type",
|
"Select adapter type": "Select adapter type",
|
||||||
"/ or /drive": "/ or /drive",
|
"/ or /drive": "/ or /drive",
|
||||||
|
"Used Capacity": "Used Capacity",
|
||||||
|
"Capacity Usage": "Capacity Usage",
|
||||||
"Adapter Config": "Adapter Config",
|
"Adapter Config": "Adapter Config",
|
||||||
"adapter.type.local": "Local Filesystem",
|
"adapter.type.local": "Local Filesystem",
|
||||||
"adapter.type.foxel": "Foxel Node",
|
"adapter.type.foxel": "Foxel Node",
|
||||||
|
|||||||
@@ -513,6 +513,8 @@
|
|||||||
"Unique name": "唯一名称",
|
"Unique name": "唯一名称",
|
||||||
"Select adapter type": "选择适配器类型",
|
"Select adapter type": "选择适配器类型",
|
||||||
"/ or /drive": "/或/drive",
|
"/ or /drive": "/或/drive",
|
||||||
|
"Used Capacity": "已使用容量",
|
||||||
|
"Capacity Usage": "容量使用",
|
||||||
"Adapter Config": "适配器配置",
|
"Adapter Config": "适配器配置",
|
||||||
"adapter.type.local": "本地文件系统",
|
"adapter.type.local": "本地文件系统",
|
||||||
"adapter.type.foxel": "Foxel 节点",
|
"adapter.type.foxel": "Foxel 节点",
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
import { memo, useState, useEffect, useCallback } from 'react';
|
import { memo, useState, useEffect, useCallback } from 'react';
|
||||||
import { Table, Button, Space, Drawer, Form, Input, Switch, message, Typography, Popconfirm, Select } from 'antd';
|
import { Table, Button, Space, Drawer, Form, Input, Switch, message, Typography, Popconfirm, Select } from 'antd';
|
||||||
import PageCard from '../components/PageCard';
|
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';
|
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 AdaptersPage = memo(function AdaptersPage() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState<AdapterItem[]>([]);
|
const [data, setData] = useState<AdapterItem[]>([]);
|
||||||
|
const [usageMap, setUsageMap] = useState<Record<number, AdapterUsage>>({});
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<AdapterItem | null>(null);
|
const [editing, setEditing] = useState<AdapterItem | null>(null);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -16,12 +33,14 @@ const AdaptersPage = memo(function AdaptersPage() {
|
|||||||
const fetchList = useCallback(async () => {
|
const fetchList = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const [list, types] = await Promise.all([
|
const [list, types, usages] = await Promise.all([
|
||||||
adaptersApi.list(),
|
adaptersApi.list(),
|
||||||
adaptersApi.available()
|
adaptersApi.available(),
|
||||||
|
adaptersApi.usage()
|
||||||
]);
|
]);
|
||||||
setData(list);
|
setData(list);
|
||||||
setAvailableTypes(types);
|
setAvailableTypes(types);
|
||||||
|
setUsageMap(Object.fromEntries(usages.map(item => [item.id, item])));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
message.error(e.message || t('Load failed'));
|
message.error(e.message || t('Load failed'));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -142,6 +161,13 @@ const AdaptersPage = memo(function AdaptersPage() {
|
|||||||
{ title: t('Type'), dataIndex: 'type', width: 140, render: (value: string) => renderTypeLabel(value) },
|
{ 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('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('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'),
|
title: t('Enabled'),
|
||||||
dataIndex: 'enabled',
|
dataIndex: 'enabled',
|
||||||
|
|||||||
Reference in New Issue
Block a user