mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2024-11-16 11:42:21 +08:00
chore: update
This commit is contained in:
parent
d3f6822080
commit
ca97e3a3e6
|
@ -4,11 +4,14 @@ import {
|
|||
useState,
|
||||
useRef,
|
||||
SVGProps,
|
||||
useCallback,
|
||||
useMemo,
|
||||
memo,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLockFn } from "ahooks";
|
||||
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 { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { isValidUrl } from "@/utils/helper";
|
||||
|
@ -53,6 +56,36 @@ type BackupFile = IWebDavFile & {
|
|||
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) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
@ -62,13 +95,16 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
const [showPassword, setShowPassword] = useState(false);
|
||||
const usernameRef = useRef<HTMLInputElement>(null);
|
||||
const passwordRef = useRef<HTMLInputElement>(null);
|
||||
const [backupFiles, setBackupFiles] = useState<BackupFile[]>([]);
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
const [backupState, setBackupState] = useState({
|
||||
files: [] as BackupFile[],
|
||||
page: 0,
|
||||
rowsPerPage: DEFAULT_ROWS_PER_PAGE,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
const OS = getSystem();
|
||||
const urlRef = useRef<HTMLInputElement>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { register, handleSubmit, watch } = useForm<IWebDavConfig>({
|
||||
defaultValues: {
|
||||
url: webdav_url,
|
||||
|
@ -96,34 +132,36 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
}));
|
||||
|
||||
// Handle page change
|
||||
const handleChangePage = (
|
||||
_: React.MouseEvent<HTMLButtonElement> | null,
|
||||
page: number
|
||||
) => {
|
||||
console.log(page);
|
||||
setPage(page);
|
||||
};
|
||||
const handleChangePage = useCallback(
|
||||
(_: React.MouseEvent<HTMLButtonElement> | null, page: number) => {
|
||||
setBackupState((prev) => ({ ...prev, page }));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Handle rows per page change
|
||||
const handleChangeRowsPerPage = (event: any) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0); // Reset to the first page
|
||||
};
|
||||
const handleChangeRowsPerPage = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setBackupState((prev) => ({
|
||||
...prev,
|
||||
rowsPerPage: parseInt(event.target.value, 10),
|
||||
page: 0,
|
||||
}));
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const fetchAndSetBackupFiles = () => {
|
||||
setIsLoading(true); // Assuming setIsLoading is defined in your component or context to manage loading state
|
||||
|
||||
getAllBackupFiles()
|
||||
.then((files: BackupFile[]) => {
|
||||
console.log(files);
|
||||
setBackupFiles(files); // Assuming setBackupFiles is a state setter function in your component or context
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
const fetchAndSetBackupFiles = async () => {
|
||||
try {
|
||||
setBackupState((prev) => ({ ...prev, isLoading: true }));
|
||||
const files = await getAllBackupFiles();
|
||||
setBackupState((prev) => ({ ...prev, files }));
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch backup files:", error);
|
||||
Notice.error(t("Failed to fetch backup files"));
|
||||
} finally {
|
||||
setBackupState((prev) => ({ ...prev, isLoading: false }));
|
||||
}
|
||||
};
|
||||
|
||||
const checkForm = () => {
|
||||
|
@ -132,21 +170,21 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
const url = urlRef.current?.value;
|
||||
|
||||
if (!url) {
|
||||
Notice.error(t("Webdav url cannot be empty"));
|
||||
Notice.error(t("WebDAV URL Required"));
|
||||
urlRef.current?.focus();
|
||||
return;
|
||||
} else if (!isValidUrl(url)) {
|
||||
Notice.error(t("Webdav address must be url"));
|
||||
Notice.error(t("Invalid WebDAV URL"));
|
||||
urlRef.current?.focus();
|
||||
return;
|
||||
}
|
||||
if (!username) {
|
||||
Notice.error(t("Username cannot be empty"));
|
||||
Notice.error(t("Username Required"));
|
||||
usernameRef.current?.focus();
|
||||
return;
|
||||
}
|
||||
if (!password) {
|
||||
Notice.error(t("Password cannot be empty"));
|
||||
Notice.error(t("Password Required"));
|
||||
passwordRef.current?.focus();
|
||||
return;
|
||||
}
|
||||
|
@ -154,7 +192,7 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
|
||||
const submit = async (data: IWebDavConfig) => {
|
||||
checkForm();
|
||||
setIsLoading(true);
|
||||
setBackupState((prev) => ({ ...prev, isLoading: true }));
|
||||
await saveWebdavConfig(data.url, data.username, data.password)
|
||||
.then(() => {
|
||||
mutateVerge(
|
||||
|
@ -165,36 +203,34 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
},
|
||||
false
|
||||
);
|
||||
Notice.success(t("Webdav Config Saved Successfully"), 1500);
|
||||
Notice.success(t("WebDAV Config Saved"));
|
||||
})
|
||||
.catch((e) => {
|
||||
Notice.error(t("Webdav Config Save Failed", { error: e }), 3000);
|
||||
Notice.error(t("WebDAV Config Save Failed", { error: e }), 3000);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
setBackupState((prev) => ({ ...prev, isLoading: false }));
|
||||
fetchAndSetBackupFiles();
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickShowPassword = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
const handleClickShowPassword = useCallback(() => {
|
||||
setShowPassword((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const handleBackup = useLockFn(async () => {
|
||||
checkForm();
|
||||
setIsLoading(true);
|
||||
await createWebdavBackup()
|
||||
.then(() => {
|
||||
Notice.success(t("Backup Successfully"), 1500);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
fetchAndSetBackupFiles();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e, "backup failed");
|
||||
Notice.error(t("Backup Failed", { error: e }), 3000);
|
||||
});
|
||||
try {
|
||||
checkForm();
|
||||
setBackupState((prev) => ({ ...prev, isLoading: true }));
|
||||
await createWebdavBackup();
|
||||
Notice.success(t("Backup Created"));
|
||||
await fetchAndSetBackupFiles();
|
||||
} catch (error) {
|
||||
console.error("Backup failed:", error);
|
||||
Notice.error(t("Backup Failed", { error }));
|
||||
} finally {
|
||||
setBackupState((prev) => ({ ...prev, isLoading: false }));
|
||||
}
|
||||
});
|
||||
|
||||
const getAllBackupFiles = async () => {
|
||||
|
@ -202,10 +238,8 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
return files
|
||||
.map((file) => {
|
||||
const platform = file.filename.split("-")[0];
|
||||
const fileBackupTimeStr = file.filename.match(
|
||||
/\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/
|
||||
)!;
|
||||
const backupTime = dayjs(fileBackupTimeStr[0], "YYYY-MM-DD_HH-mm-ss");
|
||||
const fileBackupTimeStr = file.filename.match(FILENAME_PATTERN)!;
|
||||
const backupTime = dayjs(fileBackupTimeStr[0], DATE_FORMAT);
|
||||
const allowApply = OS === platform;
|
||||
return {
|
||||
...file,
|
||||
|
@ -217,10 +251,17 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
.sort((a, b) => (a.backup_time.isAfter(b.backup_time) ? -1 : 1));
|
||||
};
|
||||
|
||||
const datasource = backupFiles.slice(
|
||||
page * rowsPerPage,
|
||||
page * rowsPerPage + rowsPerPage
|
||||
);
|
||||
const datasource = useMemo(() => {
|
||||
return backupState.files.slice(
|
||||
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 (
|
||||
<BaseDialog
|
||||
|
@ -234,207 +275,261 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
<Box sx={{ maxWidth: 800 }}>
|
||||
<BaseLoadingOverlay isLoading={isLoading} />
|
||||
<BaseLoadingOverlay isLoading={backupState.isLoading} />
|
||||
<Paper elevation={2} sx={{ padding: 2 }}>
|
||||
<form onSubmit={handleSubmit(submit)}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={9}>
|
||||
<Grid container spacing={2}>
|
||||
{/* WebDAV Server Address */}
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="WebDAV Server URL"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
{...register("url")}
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
inputRef={urlRef}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* 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>
|
||||
<WebDAVConfigForm
|
||||
onSubmit={onFormSubmit}
|
||||
initialValues={{
|
||||
url: webdav_url,
|
||||
username: webdav_username,
|
||||
password: webdav_password,
|
||||
}}
|
||||
urlRef={urlRef}
|
||||
usernameRef={usernameRef}
|
||||
passwordRef={passwordRef}
|
||||
showPassword={showPassword}
|
||||
onShowPasswordClick={handleClickShowPassword}
|
||||
webdavChanged={webdavChanged}
|
||||
webdavUrl={webdav_url}
|
||||
webdavUsername={webdav_username}
|
||||
webdavPassword={webdav_password}
|
||||
handleBackup={handleBackup}
|
||||
register={register}
|
||||
/>
|
||||
<Divider sx={{ marginY: 2 }} />
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>文件名称</TableCell>
|
||||
<TableCell>时间</TableCell>
|
||||
<TableCell align="right">操作</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="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>
|
||||
<BackupTable
|
||||
datasource={datasource}
|
||||
page={backupState.page}
|
||||
rowsPerPage={backupState.rowsPerPage}
|
||||
onPageChange={(_, page) => handleChangePage(null, page)}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
totalCount={backupState.files.length}
|
||||
/>
|
||||
</Paper>
|
||||
</Box>
|
||||
</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>) {
|
||||
return (
|
||||
<svg
|
||||
|
|
|
@ -371,5 +371,24 @@
|
|||
"Switched to _clash Core": "Switched to {{core}} Core",
|
||||
"GeoData Updated": "GeoData Updated",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@
|
|||
"Install": "نصب",
|
||||
"Uninstall": "حذف نصب",
|
||||
"Disable Service Mode": "غیرفعال کردن حالت سرویس",
|
||||
"System Proxy": "پراکسی سیستم",
|
||||
"System Proxy": "پراکسی س<EFBFBD><EFBFBD>ستم",
|
||||
"System Proxy Info": "به امکانات تنظیم پروکسی سیستم عامل دسترسی پیدا کنید. اگر فعالسازی ناموفق بود، پروکسی سیستم عامل را بهصورت دستی تغییر دهید",
|
||||
"System Proxy Setting": "تنظیمات پراکسی سیستم",
|
||||
"Current System Proxy": "پراکسی سیستم فعلی",
|
||||
|
@ -324,7 +324,7 @@
|
|||
"Default Latency Test Info": "فقط برای تست درخواستهای کلاینت HTTP استفاده میشود و بر فایل پیکربندی تأثیری نخواهد داشت",
|
||||
"Default Latency Timeout": "زمان انتظار تأخیر پیشفرض",
|
||||
"Hotkey Setting": "تنظیمات کلیدهای میانبر",
|
||||
"open_or_close_dashboard": "باز/بستن داشبورد",
|
||||
"open_or_close_dashboard": "باز/بستن داشبرد",
|
||||
"clash_mode_rule": "حالت قانون",
|
||||
"clash_mode_global": "حالت جهانی",
|
||||
"clash_mode_direct": "حالت مستقیم",
|
||||
|
@ -369,5 +369,24 @@
|
|||
"Switched to _clash Core": "تغییر به هسته {{core}}",
|
||||
"GeoData Updated": "GeoData بهروزرسانی شد",
|
||||
"Currently on the Latest Version": "در حال حاضر در آخرین نسخه",
|
||||
"Import Subscription Successfully": "عضویت با موفقیت وارد شد"
|
||||
"Import Subscription Successfully": "عضویت با موفقیت وارد شد",
|
||||
"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": "بازیابی پشتیبان"
|
||||
}
|
||||
|
|
|
@ -203,7 +203,7 @@
|
|||
"DNS Hijack": "DNS-перехват",
|
||||
"MTU": "Максимальная единица передачи",
|
||||
"Service Mode": "Режим сервиса",
|
||||
"Service Mode Info": "Установите сервисный режим перед включением режима TUN. Процесс ядра, запущенный службой, может получить разрешение на установку виртуальной сетевой карты (режим TUN).",
|
||||
"Service Mode Info": "Установите серви<EFBFBD><EFBFBD>ный режим перед включением режима TUN. Процесс ядра, запущенный службой, может получить разрешение на установку витуальной сетевой карты (режим TUN).",
|
||||
"Current State": "Текущее состояние",
|
||||
"pending": "Ожидающий",
|
||||
"installed": "Установленный",
|
||||
|
@ -239,7 +239,7 @@
|
|||
"Manual": "Документация",
|
||||
"Github Repo": "GitHub репозиторий",
|
||||
"Clash Setting": "Настройки Clash",
|
||||
"Allow Lan": "Разрешить локальную сеть",
|
||||
"Allow Lan": "Разрешить локльную сеть",
|
||||
"Network Interface": "Сетевой интерфейс",
|
||||
"Ip Address": "IP адрес",
|
||||
"Mac Address": "MAC адрес",
|
||||
|
@ -314,7 +314,7 @@
|
|||
"Auto Close Connections Info": "Завершить установленные соединения при изменении выбора группы прокси или режима прокси",
|
||||
"Auto Check Update": "Автоматическая проверка обновлений",
|
||||
"Enable Builtin Enhanced": "Включить встроенные улучшения",
|
||||
"Enable Builtin Enhanced Info": "Обработка совместимости для файла конфигурации",
|
||||
"Enable Builtin Enhanced Info": "Обработк совместимости для файла конфигурации",
|
||||
"Proxy Layout Columns": "Количество столбцов в макете прокси",
|
||||
"Auto Columns": "Авто колонки",
|
||||
"Auto Log Clean": "Автоматическая очистка журналов",
|
||||
|
@ -369,5 +369,11 @@
|
|||
"Switched to _clash Core": "Переключено на ядра {{core}}",
|
||||
"GeoData Updated": "GeoData Обновлена",
|
||||
"Currently on the Latest Version": "В настоящее время используется последняя версия",
|
||||
"Import subscription successful": "Импорт подписки успешно"
|
||||
"Import subscription successful": "Импорт подписки успешно",
|
||||
"WebDAV Server URL": "URL-адрес сервера WebDAV",
|
||||
"Username": "Имя пользователя",
|
||||
"Password": "Пароль",
|
||||
"Backup": "Резервное копирование",
|
||||
"Delete Backup": "Удалить резервную копию",
|
||||
"Restore Backup": "Восстановить резервную копию"
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
"PROCESS-PATH-REGEX": "正则匹配完整进程路径",
|
||||
"NETWORK": "匹配传输协议(tcp/udp)",
|
||||
"UID": "匹配Linux USER ID",
|
||||
"IN-TYPE": "匹配入站类型",
|
||||
"IN-TYPE": "匹配站类型",
|
||||
"IN-USER": "匹配入站用户名",
|
||||
"IN-NAME": "匹配入站名称",
|
||||
"SUB-RULE": "子规则",
|
||||
|
@ -128,7 +128,7 @@
|
|||
"Routing Mark": "路由标记",
|
||||
"Include All": "引入所有出站代理、代理集合",
|
||||
"Include All Providers": "引入所有代理集合",
|
||||
"Include All Proxies": "引入所有出站代理",
|
||||
"Include All Proxies": "入所有出站代理",
|
||||
"Exclude Filter": "排除节点",
|
||||
"Exclude Type": "排除节点类型",
|
||||
"Disable UDP": "禁用UDP",
|
||||
|
@ -161,7 +161,7 @@
|
|||
"Script Console": "脚本控制台输出",
|
||||
"To Top": "移到最前",
|
||||
"To End": "移到末尾",
|
||||
"Connections": "连接",
|
||||
"Connections": "接",
|
||||
"Table View": "表格视图",
|
||||
"List View": "列表视图",
|
||||
"Close All": "关闭全部",
|
||||
|
@ -197,7 +197,7 @@
|
|||
"Reset to Default": "重置为默认值",
|
||||
"Tun Mode Info": "Tun(虚拟网卡)模式接管系统所有流量,启用时无须打开系统代理",
|
||||
"Stack": "Tun 模式堆栈",
|
||||
"System and Mixed Can Only be Used in Service Mode": "System 和 Mixed 只能在服务模式下使用",
|
||||
"System and Mixed Can Only be Used in Service Mode": "System 和 Mixed <EFBFBD><EFBFBD><EFBFBD>能在服务模式使用",
|
||||
"Device": "Tun 网卡名称",
|
||||
"Auto Route": "自动设置全局路由",
|
||||
"Strict Route": "严格路由",
|
||||
|
@ -361,7 +361,7 @@
|
|||
"Service Uninstalled Successfully": "已成功卸载服务",
|
||||
"Proxy Daemon Duration Cannot be Less than 1 Second": "代理守护间隔时间不得低于1秒",
|
||||
"Invalid Bypass Format": "无效的代理绕过格式",
|
||||
"Clash Port Modified": "Clash 端口已修改",
|
||||
"Clash Port Modified": "Clash <EFBFBD><EFBFBD>口已修改",
|
||||
"Port Conflict": "端口冲突",
|
||||
"Restart Application to Apply Modifications": "重启Verge以应用修改",
|
||||
"External Controller Address Modified": "外部控制器监听地址已修改",
|
||||
|
@ -371,5 +371,25 @@
|
|||
"Switched to _clash Core": "已切换至 {{core}} 内核",
|
||||
"GeoData Updated": "已更新 GeoData",
|
||||
"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