+
+ {contextHolder}
+
+
+ } onClick={() => { updatePath(getParentPath(path!)) }} disabled={!storageName} type='text' title={t('parent_directory')} />
+
+
+ } onClick={fileInfo} disabled={!storageName} type='text' title={t('refresh')} />
+
+
+
+
+
+ { return path! }} onChange={(value) => { setPathTemp(value) }} onPressEnter={() => { updatePath(pathTemp) }} />
+
+
+
+ } onClick={MakeDir} disabled={!storageName && !path} type='text' title={t('create_directory')} />
+
+
+ } onClick={UploadFile} disabled={!storageName && !path} type='text' title={t('upload_file')} />
+
+
+
+
+ {
+ clipList.forEach((item) => {
+ if (item.isMove) {
+ if (item.isDir) {
+ moveDir(item.storageName, item.path, storageName!, path!);
+ } else {
+ moveFile(item.storageName, item.path, storageName!, path!);
+ }
+ } else {
+ if (item.isDir) {
+ copyDir(item.storageName, item.path, storageName!, path!);
+ } else {
+ copyFile(item.storageName, item.path, storageName!, path!)
+ }
+ }
+ })
+ setClipList([])
+ Notification.success({
+ title: t('success'),
+ content: t('transm_task_created'),
+ })
+ }} key='p' disabled={!storageName && !path}>{t('paste')}({clipList.length})
+ setClipList([])} key='q'>{t('empty_the_clipboard')}
+ } position='bl'>
+ } type='text' />
+
+
+
+
+
+
+
+ {storageName ?
+ <>{
+ fileList ?
+
}
+ data={
+ fileList.map((item) => {
+ return {
+ ...item, fileName:
{ item.IsDir && updatePath(item.Path) }}>
{item.Name},
+ fileSize: (item.Size != -1 ? formatSize(item.Size) : t('dir')),
+ fileModTime: (new Date(item.ModTime)).toLocaleString(),
+ actions:
+
+ }
+ })} />
+ : ''
+ }
+ > :
+ !storageName &&
{t('please_select_storage')}
+ }
+
+
+ )
+}
+
+
+
+export { Explorer_page }
\ No newline at end of file
diff --git a/src/page/storage/storage.tsx b/src/page/storage/storage.tsx
new file mode 100644
index 0000000..8ae8010
--- /dev/null
+++ b/src/page/storage/storage.tsx
@@ -0,0 +1,87 @@
+import { Button, Popconfirm, Space } from "@arco-design/web-react"
+import { useTranslation } from 'react-i18next';
+import { delStorage, reupStorage } from "../../controller/storage/storage"
+import { rcloneInfo } from "../../services/rclone"
+import { useEffect, useReducer, useState } from "react";
+import { hooks } from "../../services/hook";
+import { useNavigate } from "react-router-dom";
+
+import { Table, TableColumnProps } from '@arco-design/web-react';
+import { NoData_module } from "../other/noData";
+import { searchStorage } from "../../controller/storage/listAll";
+
+
+function Storage_page() {
+ const { t } = useTranslation()
+ const [ignored, forceUpdate] = useReducer(x => x + 1, 0);//刷新组件
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ hooks.upStorage = forceUpdate
+ }, [ignored])
+
+ const columns: TableColumnProps[] = [
+ {
+ title: t('name'),
+ dataIndex: 'name',
+
+ },
+ {
+ title: t('type'),
+ dataIndex: 'type',
+ },
+ {
+ title: t('actions'),
+ dataIndex: 'actions',
+ align: 'right'
+ }
+ ]
+
+ return (
+
+
+
+ { navigate('./add') }} type='primary'>{t('add')}
+ {t('refresh')}
+
+
+
+
+
+
} columns={columns} pagination={false} data={
+ rcloneInfo.storageList.map((item) => {
+ return {
+ ...item,
+ type: searchStorage(item.type).name,
+ actions:
+ {
+ delStorage(item.name)
+ }}
+ >
+ {t('delete')}
+
+
+ navigate('./add?edit=true&name=' + item.name + '&type=' + item.type)}>{t('edit')}
+ navigate('/storage/explorer?name=' + item.name)} >{t('explorer')}
+ navigate('/mount/add?name=' + item.name)} type='primary'>{t('mount')}
+
+
+ }
+ })} />
+
+
+ )
+}
+
+
+
+
+
+
+
+
+
+export { Storage_page }
\ No newline at end of file
diff --git a/src/page/task/add.tsx b/src/page/task/add.tsx
new file mode 100644
index 0000000..db0037c
--- /dev/null
+++ b/src/page/task/add.tsx
@@ -0,0 +1,309 @@
+import { Button, Divider, Form, Grid, Input, InputNumber, Notification, Select, Space, Tooltip } from '@arco-design/web-react';
+import React, { useReducer, useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { nmConfig, roConfig } from '../../services/config';
+import { TaskListItem } from '../../type/config';
+import { rcloneInfo } from '../../services/rclone';
+import { IconQuestionCircle } from '@arco-design/web-react/icon';
+import { formatPathRclone } from '../../controller/storage/storage';
+import { useNavigate } from 'react-router-dom';
+import { saveTask } from '../../controller/task/task';
+const Row = Grid.Row;
+const Col = Grid.Col;
+
+const formatPath = (path: string) => {
+ path = path.replace(/\\/g, '/');
+ path = path.replace(/\/+/g, '/');
+
+ if (path.substring(0, 1) != '/') {
+ path = '/' + path;
+ }
+
+ return path
+}
+
+// 定义状态和 action 类型
+type TaskInfoState = TaskListItem;
+
+type Action =
+ | { type: 'setName'; payload: string }
+ | { type: 'setRunTypeMode'; payload: string }
+ | { type: 'setTaskType'; payload: string }
+ | { type: 'setSourceStorageName'; payload: string }
+ | { type: 'setSourcePath'; payload: string }
+ | { type: 'setTargetStorageName'; payload: string }
+ | { type: 'setTargetPath'; payload: string }
+ | { type: 'setIntervalDays'; payload: number }
+ | { type: 'setRunTime'; payload: { h: number, m: number, s: number } };
+
+// 定义 reducer 函数
+const reducer = (state: TaskInfoState, action: Action): TaskInfoState => {
+ switch (action.type) {
+ case 'setName':
+ return { ...state, name: action.payload };
+ case 'setRunTypeMode':
+ return { ...state, run: { ...state.run, mode: action.payload } };
+ case 'setTaskType':
+ return { ...state, taskType: action.payload };
+ case 'setSourceStorageName':
+ return { ...state, source: { ...state.source, storageName: action.payload } };
+ case 'setSourcePath':
+ return { ...state, source: { ...state.source, path: formatPath(action.payload) } };
+ case 'setTargetStorageName':
+ return { ...state, target: { ...state.target, storageName: action.payload } };
+ case 'setTargetPath':
+ return { ...state, target: { ...state.target, path: formatPath(action.payload) } };
+ case 'setIntervalDays':
+ return { ...state, run: { ...state.run, time: { ...state.run.time, intervalDays: action.payload } } };
+ case 'setRunTime':
+ return { ...state, run: { ...state.run, time: { ...state.run.time, ...action.payload } } };
+ default:
+ throw new Error('Invalid action');
+ }
+};
+
+function AddTask_page() {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const [taskInfo, dispatch] = useReducer(reducer, {
+ name: 'task_' + (nmConfig.task ? nmConfig.task.length + 1 : 1),
+ taskType: roConfig.options.task.taskType.select[roConfig.options.task.taskType.defIndex],
+ source: {
+ storageName:
+ rcloneInfo.storageList && rcloneInfo.storageList.length > 0
+ ? rcloneInfo.storageList[0].name
+ : '',
+ path: '/',
+ },
+ target: {
+ storageName:
+ rcloneInfo.storageList && rcloneInfo.storageList.length > 0
+ ? (rcloneInfo.storageList.length > 1
+ ? rcloneInfo.storageList[1].name
+ : rcloneInfo.storageList[0].name)
+ : '',
+ path: '/',
+ },
+ run: {
+ mode: roConfig.options.task.runMode.select[roConfig.options.task.runMode.defIndex], time: {
+ intervalDays: 1,
+ h: 10,
+ m: 30,
+ s: 0,
+ }
+ },
+ enable: true,
+ });
+
+ const [timeMultiplier, setTimeMultiplier] = useState({
+ ...roConfig.options.task.dateMultiplier.select[roConfig.options.task.dateMultiplier.defIndex],
+ multiplicand: 1
+ })
+
+ useEffect(() => {
+ if (taskInfo.run.mode === 'time') {
+ setTimeMultiplier({ ...roConfig.options.task.dateMultiplier.select[roConfig.options.task.dateMultiplier.defIndex], multiplicand: 1 })
+ } else if (taskInfo.run.mode === 'interval') {
+ setTimeMultiplier({ ...roConfig.options.task.intervalMultiplier.select[roConfig.options.task.intervalMultiplier.defIndex], multiplicand: 1 })
+ }
+ }, [taskInfo.run.mode])
+
+
+ return (
+
+
{t('add_task')}
+
+ dispatch({ type: 'setName', payload: value })}
+ placeholder={t('please_input')}
+ />
+
+
+
+
+ {taskInfo.run.mode != 'start' && taskInfo.run.mode !== 'disposable' &&
+ <>
+
+
+
+
+
+
+ {
+ setTimeMultiplier({ ...timeMultiplier, multiplicand: value });
+ }
+ } />
+
+
+
+
+ {
+ taskInfo.run.mode === 'time' && <>
+
+
+
+ dispatch({ type: 'setRunTime', payload: { ...taskInfo.run.time, h: value } })}
+ />
+
+
+ dispatch({ type: 'setRunTime', payload: { ...taskInfo.run.time, m: value } })}
+ />
+
+
+ dispatch({ type: 'setRunTime', payload: { ...taskInfo.run.time, s: value } })}
+ />
+
+
+
+ >
+ }
+ >
+ }
+
+
+
+
+
+
+
+
+
+
+
+ dispatch({ type: 'setSourcePath', payload: value })}
+ disabled={!taskInfo.source.storageName}
+ />
+
+
+
+ }>
+
+
+
+
+
+ {taskInfo.taskType !== 'delete' && (
+
+
+
+
+
+
+ dispatch({ type: 'setTargetPath', payload: value })}
+ disabled={!taskInfo.target.storageName}
+ />
+
+
+
+ }>
+
+
+
+
+ )}
+
+
+
+ {
+ navigate('/task/')
+ }}>{t('step_back')}
+ {
+ if (taskInfo.run.mode === 'time') {
+ taskInfo.run.time.intervalDays = timeMultiplier.multiplicand * timeMultiplier.value;
+ } else if (taskInfo.run.mode === 'interval') {
+ taskInfo.run.interval = timeMultiplier.multiplicand * timeMultiplier.value;
+ }
+ if (nmConfig.task && nmConfig.task.forEach(item => item.name == taskInfo.name)! || !taskInfo.name) {
+ Notification.error({
+ title: t('error'),
+ content: t('the_task_name_is_illegal'),
+ })
+ } else if(!taskInfo.source.storageName || !taskInfo.source.path|| (taskInfo.taskType!== 'delete' &&(!taskInfo.target.storageName || !taskInfo.target.path))){
+ Notification.error({
+ title: t('error'),
+ content: t('the_path_is_illegal'),
+ })
+ }else if(taskInfo.taskType!== 'delete' && taskInfo.source.path===taskInfo.target.path&&taskInfo.source.storageName===taskInfo.target.storageName){
+ Notification.error({
+ title: t('error'),
+ content: t('same_source_and_target'),
+ })
+ } else {
+ if (saveTask(taskInfo)) {
+ Notification.success({
+ title: t('success'),
+ content: t('task_added_successfully'),
+ })
+ navigate('/task/')
+ }
+ }
+ }}>{t('add')}
+
+
+
+ );
+}
+
+
+
+export { AddTask_page };
\ No newline at end of file
diff --git a/src/page/task/task.tsx b/src/page/task/task.tsx
new file mode 100644
index 0000000..c33d829
--- /dev/null
+++ b/src/page/task/task.tsx
@@ -0,0 +1,64 @@
+import React, { useReducer } from 'react'
+import { DevTips_module } from '../other/devTips'
+import { Button, Space, Table, TableColumnProps } from '@arco-design/web-react'
+import { useTranslation } from 'react-i18next'
+import { nmConfig } from '../../services/config'
+import { useNavigate } from 'react-router-dom'
+import { delTask } from '../../controller/task/task'
+import { NoData_module } from '../other/noData'
+
+function Task_page() {
+ const { t } = useTranslation()
+ const navigate = useNavigate();
+ const [ignored, forceUpdate] = useReducer(x => x + 1, 0);//刷新组件
+
+ const columns: TableColumnProps[] = [
+ {
+ title: t('task_name'),
+ dataIndex: 'name',
+ },
+ {
+ title: t('state'),
+ dataIndex: 'state',
+ }, {
+ title: t('cycle'),
+ dataIndex: 'cycle',
+ }
+ , {
+ title: t('run_info'),
+ dataIndex: 'runInfo',
+ },
+ {
+ title: t('actions'),
+ dataIndex: 'actions',
+ align:'right'
+ }
+ ];
+ console.log(nmConfig.task);
+
+ return (
+
+
+
+ { navigate('/task/add') }}>{t('add')}
+ { forceUpdate() }}>{t('refresh')}
+
+
+
+
} data={nmConfig.task.map((taskItem) => {
+ return {
+ ...taskItem,
+ state: taskItem.enable ? t('enabled') : t('disabled'),
+ cycle: t('task_run_mode_' + taskItem.run.mode),
+ runInfo:taskItem.runInfo?.msg,
+ actions: <>
+
{delTask(taskItem.name);forceUpdate()}}>{t('delete')}
+ >
+ }
+ })} />
+
+
+ )
+}
+
+export { Task_page }
\ No newline at end of file
diff --git a/src/page/transmit/transmit.tsx b/src/page/transmit/transmit.tsx
new file mode 100644
index 0000000..67cb677
--- /dev/null
+++ b/src/page/transmit/transmit.tsx
@@ -0,0 +1,135 @@
+import React, { useEffect, useState } from 'react'
+import { rcloneInfo, rcloneStatsHistory } from '../../services/rclone'
+import { hooks } from '../../services/hook'
+import { RcloneTransferItem } from '../../type/rclone/stats'
+import { Card, Descriptions, List, Progress, Space, Statistic, Grid, Typography } from '@arco-design/web-react'
+import { formatETA, formatSize } from '../../utils/utils'
+import { Area } from '@ant-design/charts'
+import { NoData_module } from '../other/noData'
+import { useTranslation } from 'react-i18next'
+const Row = Grid.Row;
+const Col = Grid.Col;
+
+function Transmit_page() {
+ const { t } = useTranslation()
+ const [transmitList, setTransmitList] = useState