mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2024-11-16 11:42:21 +08:00
fix: translation
This commit is contained in:
parent
d3f6822080
commit
e5740579f4
14
README.md
14
README.md
|
@ -32,7 +32,7 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||||
[狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
[狗狗加速 —— 技术流机场 Doggygo VPN](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||||
|
|
||||||
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
|
- 高性能海外机场,免费试用,优惠套餐,解锁流媒体,全球首家支持 Hysteria 协议。
|
||||||
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:https://verge.dginv.click/#/register?code=oaxsAGo6
|
- 使用 Clash Verge 专属邀请链接注册送 3 天,每天 1G 流量免费试用:[点此注册](https://verge.dginv.click/#/register?code=oaxsAGo6)
|
||||||
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
|
- Clash Verge 专属 8 折优惠码: verge20 (仅有 500 份)
|
||||||
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
|
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
|
||||||
- 海外团队,无跑路风险,高达 50% 返佣
|
- 海外团队,无跑路风险,高达 50% 返佣
|
||||||
|
@ -43,11 +43,13 @@ Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
- 基于性能强劲的 Rust 和 Tauri 2 框架
|
||||||
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
|
- 内置[Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo)内核,并支持切换 `Alpha` 版本内核。
|
||||||
- Improved UI and supports custom theme color.
|
- 简洁美观的用户界面,支持自定义主题颜色、代理组/托盘图标以及 `CSS Injection`。
|
||||||
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
- 配置文件管理和增强(Merge 和 Script),配置文件语法提示。
|
||||||
- System proxy setting and guard.
|
- 系统代理和守卫、`TUN(虚拟网卡)` 模式。
|
||||||
|
- 可视化节点和规则编辑
|
||||||
|
- WebDav 配置备份和同步
|
||||||
|
|
||||||
### FAQ
|
### FAQ
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,14 @@ import {
|
||||||
useState,
|
useState,
|
||||||
useRef,
|
useRef,
|
||||||
SVGProps,
|
SVGProps,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
memo,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm, UseFormRegister } from "react-hook-form";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||||
import { isValidUrl } from "@/utils/helper";
|
import { isValidUrl } from "@/utils/helper";
|
||||||
|
@ -53,6 +56,36 @@ type BackupFile = IWebDavFile & {
|
||||||
allow_apply: boolean;
|
allow_apply: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface BackupTableProps {
|
||||||
|
datasource: BackupFile[];
|
||||||
|
page: number;
|
||||||
|
rowsPerPage: number;
|
||||||
|
onPageChange: (event: any, newPage: number) => void;
|
||||||
|
onRowsPerPageChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebDAVConfigFormProps {
|
||||||
|
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||||
|
initialValues: Partial<IWebDavConfig>;
|
||||||
|
urlRef: React.RefObject<HTMLInputElement>;
|
||||||
|
usernameRef: React.RefObject<HTMLInputElement>;
|
||||||
|
passwordRef: React.RefObject<HTMLInputElement>;
|
||||||
|
showPassword: boolean;
|
||||||
|
onShowPasswordClick: () => void;
|
||||||
|
webdavChanged: boolean;
|
||||||
|
webdavUrl: string | undefined | null;
|
||||||
|
webdavUsername: string | undefined | null;
|
||||||
|
webdavPassword: string | undefined | null;
|
||||||
|
handleBackup: () => Promise<void>;
|
||||||
|
register: UseFormRegister<IWebDavConfig>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将魔法数字和配置提取为常量
|
||||||
|
const DEFAULT_ROWS_PER_PAGE = 5;
|
||||||
|
const DATE_FORMAT = "YYYY-MM-DD_HH-mm-ss";
|
||||||
|
const FILENAME_PATTERN = /\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/;
|
||||||
|
|
||||||
export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
@ -62,13 +95,16 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const usernameRef = useRef<HTMLInputElement>(null);
|
const usernameRef = useRef<HTMLInputElement>(null);
|
||||||
const passwordRef = useRef<HTMLInputElement>(null);
|
const passwordRef = useRef<HTMLInputElement>(null);
|
||||||
const [backupFiles, setBackupFiles] = useState<BackupFile[]>([]);
|
const [backupState, setBackupState] = useState({
|
||||||
const [page, setPage] = useState(0);
|
files: [] as BackupFile[],
|
||||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
page: 0,
|
||||||
|
rowsPerPage: DEFAULT_ROWS_PER_PAGE,
|
||||||
|
isLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
const OS = getSystem();
|
const OS = getSystem();
|
||||||
const urlRef = useRef<HTMLInputElement>(null);
|
const urlRef = useRef<HTMLInputElement>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const { register, handleSubmit, watch } = useForm<IWebDavConfig>({
|
const { register, handleSubmit, watch } = useForm<IWebDavConfig>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
url: webdav_url,
|
url: webdav_url,
|
||||||
|
@ -96,34 +132,36 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Handle page change
|
// Handle page change
|
||||||
const handleChangePage = (
|
const handleChangePage = useCallback(
|
||||||
_: React.MouseEvent<HTMLButtonElement> | null,
|
(_: React.MouseEvent<HTMLButtonElement> | null, page: number) => {
|
||||||
page: number
|
setBackupState((prev) => ({ ...prev, page }));
|
||||||
) => {
|
},
|
||||||
console.log(page);
|
[]
|
||||||
setPage(page);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
// Handle rows per page change
|
// Handle rows per page change
|
||||||
const handleChangeRowsPerPage = (event: any) => {
|
const handleChangeRowsPerPage = useCallback(
|
||||||
setRowsPerPage(parseInt(event.target.value, 10));
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setPage(0); // Reset to the first page
|
setBackupState((prev) => ({
|
||||||
};
|
...prev,
|
||||||
|
rowsPerPage: parseInt(event.target.value, 10),
|
||||||
|
page: 0,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const fetchAndSetBackupFiles = () => {
|
const fetchAndSetBackupFiles = async () => {
|
||||||
setIsLoading(true); // Assuming setIsLoading is defined in your component or context to manage loading state
|
try {
|
||||||
|
setBackupState((prev) => ({ ...prev, isLoading: true }));
|
||||||
getAllBackupFiles()
|
const files = await getAllBackupFiles();
|
||||||
.then((files: BackupFile[]) => {
|
setBackupState((prev) => ({ ...prev, files }));
|
||||||
console.log(files);
|
} catch (error) {
|
||||||
setBackupFiles(files); // Assuming setBackupFiles is a state setter function in your component or context
|
console.error("Failed to fetch backup files:", error);
|
||||||
})
|
Notice.error(t("Failed to fetch backup files"));
|
||||||
.catch((e) => {
|
} finally {
|
||||||
console.error(e);
|
setBackupState((prev) => ({ ...prev, isLoading: false }));
|
||||||
})
|
}
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkForm = () => {
|
const checkForm = () => {
|
||||||
|
@ -132,21 +170,21 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const url = urlRef.current?.value;
|
const url = urlRef.current?.value;
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
Notice.error(t("Webdav url cannot be empty"));
|
Notice.error(t("WebDAV URL Required"));
|
||||||
urlRef.current?.focus();
|
urlRef.current?.focus();
|
||||||
return;
|
return;
|
||||||
} else if (!isValidUrl(url)) {
|
} else if (!isValidUrl(url)) {
|
||||||
Notice.error(t("Webdav address must be url"));
|
Notice.error(t("Invalid WebDAV URL"));
|
||||||
urlRef.current?.focus();
|
urlRef.current?.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!username) {
|
if (!username) {
|
||||||
Notice.error(t("Username cannot be empty"));
|
Notice.error(t("Username Required"));
|
||||||
usernameRef.current?.focus();
|
usernameRef.current?.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!password) {
|
if (!password) {
|
||||||
Notice.error(t("Password cannot be empty"));
|
Notice.error(t("Password Required"));
|
||||||
passwordRef.current?.focus();
|
passwordRef.current?.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -154,7 +192,7 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
|
|
||||||
const submit = async (data: IWebDavConfig) => {
|
const submit = async (data: IWebDavConfig) => {
|
||||||
checkForm();
|
checkForm();
|
||||||
setIsLoading(true);
|
setBackupState((prev) => ({ ...prev, isLoading: true }));
|
||||||
await saveWebdavConfig(data.url, data.username, data.password)
|
await saveWebdavConfig(data.url, data.username, data.password)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutateVerge(
|
mutateVerge(
|
||||||
|
@ -165,36 +203,34 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
Notice.success(t("Webdav Config Saved Successfully"), 1500);
|
Notice.success(t("WebDAV Config Saved"));
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
Notice.error(t("Webdav Config Save Failed", { error: e }), 3000);
|
Notice.error(t("WebDAV Config Save Failed", { error: e }), 3000);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoading(false);
|
setBackupState((prev) => ({ ...prev, isLoading: false }));
|
||||||
fetchAndSetBackupFiles();
|
fetchAndSetBackupFiles();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickShowPassword = () => {
|
const handleClickShowPassword = useCallback(() => {
|
||||||
setShowPassword(!showPassword);
|
setShowPassword((prev) => !prev);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleBackup = useLockFn(async () => {
|
const handleBackup = useLockFn(async () => {
|
||||||
checkForm();
|
try {
|
||||||
setIsLoading(true);
|
checkForm();
|
||||||
await createWebdavBackup()
|
setBackupState((prev) => ({ ...prev, isLoading: true }));
|
||||||
.then(() => {
|
await createWebdavBackup();
|
||||||
Notice.success(t("Backup Successfully"), 1500);
|
Notice.success(t("Backup Created"));
|
||||||
})
|
await fetchAndSetBackupFiles();
|
||||||
.finally(() => {
|
} catch (error) {
|
||||||
setIsLoading(false);
|
console.error("Backup failed:", error);
|
||||||
fetchAndSetBackupFiles();
|
Notice.error(t("Backup Failed", { error }));
|
||||||
})
|
} finally {
|
||||||
.catch((e) => {
|
setBackupState((prev) => ({ ...prev, isLoading: false }));
|
||||||
console.log(e, "backup failed");
|
}
|
||||||
Notice.error(t("Backup Failed", { error: e }), 3000);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getAllBackupFiles = async () => {
|
const getAllBackupFiles = async () => {
|
||||||
|
@ -202,10 +238,8 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
return files
|
return files
|
||||||
.map((file) => {
|
.map((file) => {
|
||||||
const platform = file.filename.split("-")[0];
|
const platform = file.filename.split("-")[0];
|
||||||
const fileBackupTimeStr = file.filename.match(
|
const fileBackupTimeStr = file.filename.match(FILENAME_PATTERN)!;
|
||||||
/\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/
|
const backupTime = dayjs(fileBackupTimeStr[0], DATE_FORMAT);
|
||||||
)!;
|
|
||||||
const backupTime = dayjs(fileBackupTimeStr[0], "YYYY-MM-DD_HH-mm-ss");
|
|
||||||
const allowApply = OS === platform;
|
const allowApply = OS === platform;
|
||||||
return {
|
return {
|
||||||
...file,
|
...file,
|
||||||
|
@ -217,10 +251,17 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
.sort((a, b) => (a.backup_time.isAfter(b.backup_time) ? -1 : 1));
|
.sort((a, b) => (a.backup_time.isAfter(b.backup_time) ? -1 : 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
const datasource = backupFiles.slice(
|
const datasource = useMemo(() => {
|
||||||
page * rowsPerPage,
|
return backupState.files.slice(
|
||||||
page * rowsPerPage + rowsPerPage
|
backupState.page * backupState.rowsPerPage,
|
||||||
);
|
backupState.page * backupState.rowsPerPage + backupState.rowsPerPage
|
||||||
|
);
|
||||||
|
}, [backupState.files, backupState.page, backupState.rowsPerPage]);
|
||||||
|
|
||||||
|
const onFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit(submit)(e);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
|
@ -234,207 +275,261 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
<Box sx={{ maxWidth: 800 }}>
|
<Box sx={{ maxWidth: 800 }}>
|
||||||
<BaseLoadingOverlay isLoading={isLoading} />
|
<BaseLoadingOverlay isLoading={backupState.isLoading} />
|
||||||
<Paper elevation={2} sx={{ padding: 2 }}>
|
<Paper elevation={2} sx={{ padding: 2 }}>
|
||||||
<form onSubmit={handleSubmit(submit)}>
|
<WebDAVConfigForm
|
||||||
<Grid container spacing={2}>
|
onSubmit={onFormSubmit}
|
||||||
<Grid item xs={12} sm={9}>
|
initialValues={{
|
||||||
<Grid container spacing={2}>
|
url: webdav_url,
|
||||||
{/* WebDAV Server Address */}
|
username: webdav_username,
|
||||||
<Grid item xs={12}>
|
password: webdav_password,
|
||||||
<TextField
|
}}
|
||||||
fullWidth
|
urlRef={urlRef}
|
||||||
label="WebDAV Server URL"
|
usernameRef={usernameRef}
|
||||||
variant="outlined"
|
passwordRef={passwordRef}
|
||||||
size="small"
|
showPassword={showPassword}
|
||||||
{...register("url")}
|
onShowPasswordClick={handleClickShowPassword}
|
||||||
autoCorrect="off"
|
webdavChanged={webdavChanged}
|
||||||
autoCapitalize="off"
|
webdavUrl={webdav_url}
|
||||||
spellCheck="false"
|
webdavUsername={webdav_username}
|
||||||
inputRef={urlRef}
|
webdavPassword={webdav_password}
|
||||||
/>
|
handleBackup={handleBackup}
|
||||||
</Grid>
|
register={register}
|
||||||
|
/>
|
||||||
{/* Username and Password */}
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<TextField
|
|
||||||
label="Username"
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
{...register("username")}
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
inputRef={usernameRef}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<TextField
|
|
||||||
label="Password"
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
inputRef={passwordRef}
|
|
||||||
{...register("password")}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
onClick={handleClickShowPassword}
|
|
||||||
edge="end"
|
|
||||||
>
|
|
||||||
{showPassword ? (
|
|
||||||
<VisibilityOff />
|
|
||||||
) : (
|
|
||||||
<Visibility />
|
|
||||||
)}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs={12} sm={3}>
|
|
||||||
<Stack
|
|
||||||
direction="column"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="stretch"
|
|
||||||
sx={{ height: "100%" }}
|
|
||||||
>
|
|
||||||
{webdavChanged ||
|
|
||||||
webdav_url === null ||
|
|
||||||
webdav_username == null ||
|
|
||||||
webdav_password == null ? (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
sx={{ height: "100%" }}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="success"
|
|
||||||
sx={{ height: "100%" }}
|
|
||||||
onClick={handleBackup}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Backup
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</form>
|
|
||||||
<Divider sx={{ marginY: 2 }} />
|
<Divider sx={{ marginY: 2 }} />
|
||||||
<TableContainer component={Paper}>
|
<BackupTable
|
||||||
<Table>
|
datasource={datasource}
|
||||||
<TableHead>
|
page={backupState.page}
|
||||||
<TableRow>
|
rowsPerPage={backupState.rowsPerPage}
|
||||||
<TableCell>文件名称</TableCell>
|
onPageChange={(_, page) => handleChangePage(null, page)}
|
||||||
<TableCell>时间</TableCell>
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
<TableCell align="right">操作</TableCell>
|
totalCount={backupState.files.length}
|
||||||
</TableRow>
|
/>
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{datasource.length > 0 ? (
|
|
||||||
datasource?.map((file, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
<TableCell component="th" scope="row">
|
|
||||||
{file.platform === "windows" ? (
|
|
||||||
<WindowsIcon className="h-full w-full" />
|
|
||||||
) : file.platform === "linux" ? (
|
|
||||||
<LinuxIcon className="h-full w-full" />
|
|
||||||
) : (
|
|
||||||
<MacIcon className="h-full w-full" />
|
|
||||||
)}
|
|
||||||
{file.filename}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{file.backup_time.fromNow()}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
color="secondary"
|
|
||||||
aria-label="delete"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
<Divider
|
|
||||||
orientation="vertical"
|
|
||||||
flexItem
|
|
||||||
sx={{ mx: 1, height: 24 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
color="primary"
|
|
||||||
aria-label="restore"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<RestoreIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell colSpan={3} align="center">
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
height: 150,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
color="textSecondary"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
暂无备份
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
<TablePagination
|
|
||||||
rowsPerPageOptions={[]}
|
|
||||||
component="div"
|
|
||||||
count={backupFiles.length}
|
|
||||||
rowsPerPage={rowsPerPage}
|
|
||||||
page={page}
|
|
||||||
onPageChange={handleChangePage}
|
|
||||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
|
||||||
labelRowsPerPage=""
|
|
||||||
/>
|
|
||||||
</TableContainer>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const BackupTable = memo(
|
||||||
|
({
|
||||||
|
datasource,
|
||||||
|
page,
|
||||||
|
rowsPerPage,
|
||||||
|
onPageChange,
|
||||||
|
onRowsPerPageChange,
|
||||||
|
totalCount,
|
||||||
|
}: BackupTableProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{t("Filename")}</TableCell>
|
||||||
|
<TableCell>{t("Time")}</TableCell>
|
||||||
|
<TableCell align="right">{t("Actions")}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{datasource.length > 0 ? (
|
||||||
|
datasource?.map((file, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell component="th" scope="row">
|
||||||
|
{file.platform === "windows" ? (
|
||||||
|
<WindowsIcon className="h-full w-full" />
|
||||||
|
) : file.platform === "linux" ? (
|
||||||
|
<LinuxIcon className="h-full w-full" />
|
||||||
|
) : (
|
||||||
|
<MacIcon className="h-full w-full" />
|
||||||
|
)}
|
||||||
|
{file.filename}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{file.backup_time.fromNow()}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
color="secondary"
|
||||||
|
aria-label={t("Delete")}
|
||||||
|
size="small"
|
||||||
|
title={t("Delete Backup")}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Divider
|
||||||
|
orientation="vertical"
|
||||||
|
flexItem
|
||||||
|
sx={{ mx: 1, height: 24 }}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
aria-label={t("Restore")}
|
||||||
|
size="small"
|
||||||
|
title={t("Restore Backup")}
|
||||||
|
>
|
||||||
|
<RestoreIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={3} align="center">
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: 150,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
color="textSecondary"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
{t("No Backups")}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[]}
|
||||||
|
component="div"
|
||||||
|
count={totalCount}
|
||||||
|
rowsPerPage={rowsPerPage}
|
||||||
|
page={page}
|
||||||
|
onPageChange={onPageChange}
|
||||||
|
onRowsPerPageChange={onRowsPerPageChange}
|
||||||
|
labelRowsPerPage={t("Rows per page")}
|
||||||
|
/>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const WebDAVConfigForm = memo(
|
||||||
|
({
|
||||||
|
onSubmit,
|
||||||
|
initialValues,
|
||||||
|
urlRef,
|
||||||
|
usernameRef,
|
||||||
|
passwordRef,
|
||||||
|
showPassword,
|
||||||
|
onShowPasswordClick,
|
||||||
|
webdavChanged,
|
||||||
|
webdavUrl,
|
||||||
|
webdavUsername,
|
||||||
|
webdavPassword,
|
||||||
|
handleBackup,
|
||||||
|
register,
|
||||||
|
}: WebDAVConfigFormProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12} sm={9}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label={t("WebDAV Server URL")}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
{...register("url")}
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
inputRef={urlRef}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<TextField
|
||||||
|
label={t("Username")}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
{...register("username")}
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
inputRef={usernameRef}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<TextField
|
||||||
|
label={t("Password")}
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
inputRef={passwordRef}
|
||||||
|
{...register("password")}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={onShowPasswordClick} edge="end">
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={3}>
|
||||||
|
<Stack
|
||||||
|
direction="column"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="stretch"
|
||||||
|
sx={{ height: "100%" }}
|
||||||
|
>
|
||||||
|
{webdavChanged ||
|
||||||
|
webdavUrl === null ||
|
||||||
|
webdavUsername == null ||
|
||||||
|
webdavPassword == null ? (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ height: "100%" }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{t("Save")}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="success"
|
||||||
|
sx={{ height: "100%" }}
|
||||||
|
onClick={handleBackup}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{t("Backup")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export function LinuxIcon(props: SVGProps<SVGSVGElement>) {
|
export function LinuxIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
|
|
@ -371,5 +371,24 @@
|
||||||
"Switched to _clash Core": "Switched to {{core}} Core",
|
"Switched to _clash Core": "Switched to {{core}} Core",
|
||||||
"GeoData Updated": "GeoData Updated",
|
"GeoData Updated": "GeoData Updated",
|
||||||
"Currently on the Latest Version": "Currently on the Latest Version",
|
"Currently on the Latest Version": "Currently on the Latest Version",
|
||||||
"Import Subscription Successful": "Import subscription successful"
|
"Import Subscription Successful": "Import subscription successful",
|
||||||
|
"WebDAV Server URL": "WebDAV Server URL",
|
||||||
|
"Username": "Username",
|
||||||
|
"Password": "Password",
|
||||||
|
"Backup": "Backup",
|
||||||
|
"Filename": "Filename",
|
||||||
|
"Actions": "Actions",
|
||||||
|
"Restore": "Restore",
|
||||||
|
"No Backups": "No backups available",
|
||||||
|
"WebDAV URL Required": "WebDAV URL cannot be empty",
|
||||||
|
"Invalid WebDAV URL": "Invalid WebDAV URL format",
|
||||||
|
"Username Required": "Username cannot be empty",
|
||||||
|
"Password Required": "Password cannot be empty",
|
||||||
|
"Failed to Fetch Backups": "Failed to fetch backup files",
|
||||||
|
"WebDAV Config Saved": "WebDAV configuration saved successfully",
|
||||||
|
"WebDAV Config Save Failed": "Failed to save WebDAV configuration",
|
||||||
|
"Backup Created": "Backup created successfully",
|
||||||
|
"Backup Failed": "Failed to create backup",
|
||||||
|
"Delete Backup": "Delete Backup",
|
||||||
|
"Restore Backup": "Restore Backup"
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,8 @@
|
||||||
"Append Rule": "اضافه کردن قانون به انتها",
|
"Append Rule": "اضافه کردن قانون به انتها",
|
||||||
"Prepend Group": "اضافه کردن گروه به ابتدا",
|
"Prepend Group": "اضافه کردن گروه به ابتدا",
|
||||||
"Append Group": "اضافه کردن گروه به انتها",
|
"Append Group": "اضافه کردن گروه به انتها",
|
||||||
|
"Prepend Proxy": "پیشافزودن پراکسی",
|
||||||
|
"Append Proxy": "پسافزودن پراکسی",
|
||||||
"Rule Condition Required": "شرط قانون الزامی است",
|
"Rule Condition Required": "شرط قانون الزامی است",
|
||||||
"Invalid Rule": "قانون نامعتبر",
|
"Invalid Rule": "قانون نامعتبر",
|
||||||
"Advanced": "پیشرفته",
|
"Advanced": "پیشرفته",
|
||||||
|
@ -235,6 +237,9 @@
|
||||||
"Auto Launch": "راهاندازی خودکار",
|
"Auto Launch": "راهاندازی خودکار",
|
||||||
"Silent Start": "شروع بیصدا",
|
"Silent Start": "شروع بیصدا",
|
||||||
"Silent Start Info": "برنامه را در حالت پسزمینه بدون نمایش پانل اجرا کنید",
|
"Silent Start Info": "برنامه را در حالت پسزمینه بدون نمایش پانل اجرا کنید",
|
||||||
|
"TG Channel": "کانال تلگرام",
|
||||||
|
"Manual": "راهنما",
|
||||||
|
"Github Repo": "مخزن GitHub",
|
||||||
"Clash Setting": "تنظیمات Clash",
|
"Clash Setting": "تنظیمات Clash",
|
||||||
"Allow Lan": "اجازه LAN",
|
"Allow Lan": "اجازه LAN",
|
||||||
"Network Interface": "رابط شبکه",
|
"Network Interface": "رابط شبکه",
|
||||||
|
@ -269,9 +274,6 @@
|
||||||
"Open UWP tool": "باز کردن ابزار UWP",
|
"Open UWP tool": "باز کردن ابزار UWP",
|
||||||
"Open UWP tool Info": "از ویندوز 8 به بعد، برنامههای UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شدهاند و این ابزار میتواند برای دور زدن این محدودیت استفاده شود",
|
"Open UWP tool Info": "از ویندوز 8 به بعد، برنامههای UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شدهاند و این ابزار میتواند برای دور زدن این محدودیت استفاده شود",
|
||||||
"Update GeoData": "بهروزرسانی GeoData",
|
"Update GeoData": "بهروزرسانی GeoData",
|
||||||
"TG Channel": "کانال تلگرام",
|
|
||||||
"Manual": "راهنما",
|
|
||||||
"Github Repo": "مخزن GitHub",
|
|
||||||
"Verge Setting": "تنظیمات Verge",
|
"Verge Setting": "تنظیمات Verge",
|
||||||
"Language": "زبان",
|
"Language": "زبان",
|
||||||
"Theme Mode": "حالت تم",
|
"Theme Mode": "حالت تم",
|
||||||
|
@ -369,5 +371,24 @@
|
||||||
"Switched to _clash Core": "تغییر به هسته {{core}}",
|
"Switched to _clash Core": "تغییر به هسته {{core}}",
|
||||||
"GeoData Updated": "GeoData بهروزرسانی شد",
|
"GeoData Updated": "GeoData بهروزرسانی شد",
|
||||||
"Currently on the Latest Version": "در حال حاضر در آخرین نسخه",
|
"Currently on the Latest Version": "در حال حاضر در آخرین نسخه",
|
||||||
"Import Subscription Successfully": "عضویت با موفقیت وارد شد"
|
"Import Subscription Successful": "وارد کردن اشتراک با موفقیت انجام شد",
|
||||||
|
"WebDAV Server URL": "آدرس سرور WebDAV",
|
||||||
|
"Username": "نام کاربری",
|
||||||
|
"Password": "رمز عبور",
|
||||||
|
"Backup": "پشتیبانگیری",
|
||||||
|
"Filename": "نام فایل",
|
||||||
|
"Actions": "عملیات",
|
||||||
|
"Restore": "بازیابی",
|
||||||
|
"No Backups": "هیچ پشتیبانی موجود نیست",
|
||||||
|
"WebDAV URL Required": "آدرس WebDAV نمیتواند خالی باشد",
|
||||||
|
"Invalid WebDAV URL": "فرمت آدرس WebDAV نامعتبر است",
|
||||||
|
"Username Required": "نام کاربری نمیتواند خالی باشد",
|
||||||
|
"Password Required": "رمز عبور نمیتواند خالی باشد",
|
||||||
|
"Failed to Fetch Backups": "دریافت فایلهای پشتیبان ناموفق بود",
|
||||||
|
"WebDAV Config Saved": "پیکربندی WebDAV با موفقیت ذخیره شد",
|
||||||
|
"WebDAV Config Save Failed": "ذخیره پیکربندی WebDAV ناموفق بود",
|
||||||
|
"Backup Created": "پشتیبانگیری با موفقیت ایجاد شد",
|
||||||
|
"Backup Failed": "ایجاد پشتیبان ناموفق بود",
|
||||||
|
"Delete Backup": "حذف پشتیبان",
|
||||||
|
"Restore Backup": "بازیابی پشتیبان"
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,8 @@
|
||||||
"Append Rule": "Добавить правило в конец",
|
"Append Rule": "Добавить правило в конец",
|
||||||
"Prepend Group": "Добавить группу в начало",
|
"Prepend Group": "Добавить группу в начало",
|
||||||
"Append Group": "Добавить группу в конец",
|
"Append Group": "Добавить группу в конец",
|
||||||
|
"Prepend Proxy": "Добавить прокси в начало",
|
||||||
|
"Append Proxy": "Добавить прокси в конец",
|
||||||
"Rule Condition Required": "Требуется условие правила",
|
"Rule Condition Required": "Требуется условие правила",
|
||||||
"Invalid Rule": "Недействительное правило",
|
"Invalid Rule": "Недействительное правило",
|
||||||
"Advanced": "Дополнительно",
|
"Advanced": "Дополнительно",
|
||||||
|
@ -369,5 +371,24 @@
|
||||||
"Switched to _clash Core": "Переключено на ядра {{core}}",
|
"Switched to _clash Core": "Переключено на ядра {{core}}",
|
||||||
"GeoData Updated": "GeoData Обновлена",
|
"GeoData Updated": "GeoData Обновлена",
|
||||||
"Currently on the Latest Version": "В настоящее время используется последняя версия",
|
"Currently on the Latest Version": "В настоящее время используется последняя версия",
|
||||||
"Import subscription successful": "Импорт подписки успешно"
|
"Import subscription successful": "Импорт подписки успешно",
|
||||||
|
"WebDAV Server URL": "URL-адрес сервера WebDAV",
|
||||||
|
"Username": "Имя пользователя",
|
||||||
|
"Password": "Пароль",
|
||||||
|
"Backup": "Резервное копирование",
|
||||||
|
"Filename": "Имя файла",
|
||||||
|
"Actions": "Действия",
|
||||||
|
"Restore": "Восстановить",
|
||||||
|
"No Backups": "Нет доступных резервных копий",
|
||||||
|
"WebDAV URL Required": "URL-адрес WebDAV не может быть пустым",
|
||||||
|
"Invalid WebDAV URL": "Неверный формат URL-адреса WebDAV",
|
||||||
|
"Username Required": "Имя пользователя не может быть пустым",
|
||||||
|
"Password Required": "Пароль не может быть пустым",
|
||||||
|
"Failed to Fetch Backups": "Не удалось получить файлы резервных копий",
|
||||||
|
"WebDAV Config Saved": "Конфигурация WebDAV успешно сохранена",
|
||||||
|
"WebDAV Config Save Failed": "Не удалось сохранить конфигурацию WebDAV",
|
||||||
|
"Backup Created": "Резервная копия успешно создана",
|
||||||
|
"Backup Failed": "Не удалось создать резервную копию",
|
||||||
|
"Delete Backup": "Удалить резервную копию",
|
||||||
|
"Restore Backup": "Восстановить резервную копию"
|
||||||
}
|
}
|
||||||
|
|
|
@ -371,5 +371,24 @@
|
||||||
"Switched to _clash Core": "已切换至 {{core}} 内核",
|
"Switched to _clash Core": "已切换至 {{core}} 内核",
|
||||||
"GeoData Updated": "已更新 GeoData",
|
"GeoData Updated": "已更新 GeoData",
|
||||||
"Currently on the Latest Version": "当前已是最新版本",
|
"Currently on the Latest Version": "当前已是最新版本",
|
||||||
"Import Subscription Successful": "导入订阅成功"
|
"Import Subscription Successful": "导入订阅成功",
|
||||||
|
"WebDAV Server URL": "WebDAV 服务器地址",
|
||||||
|
"Username": "用户名",
|
||||||
|
"Password": "密码",
|
||||||
|
"Backup": "备份",
|
||||||
|
"Filename": "文件名称",
|
||||||
|
"Actions": "操作",
|
||||||
|
"Restore": "恢复",
|
||||||
|
"No Backups": "暂无备份",
|
||||||
|
"WebDAV URL Required": "WebDAV 地址不能为空",
|
||||||
|
"Invalid WebDAV URL": "无效的 WebDAV 地址格式",
|
||||||
|
"Username Required": "用户名不能为空",
|
||||||
|
"Password Required": "密码不能为空",
|
||||||
|
"Failed to Fetch Backups": "获取备份文件失败",
|
||||||
|
"WebDAV Config Saved": "WebDAV 配置保存成功",
|
||||||
|
"WebDAV Config Save Failed": "WebDAV 配置保存失败",
|
||||||
|
"Backup Created": "备份创建成功",
|
||||||
|
"Backup Failed": "备份创建失败",
|
||||||
|
"Delete Backup": "删除备份",
|
||||||
|
"Restore Backup": "恢复备份"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user