Skip to content
This repository was archived by the owner on Jan 30, 2023. It is now read-only.

Commit 3cb5caf

Browse files
authored
chore: Merge pull request #36 from alist-org/dev
backup, restore and fix error open text file after close image viewer
2 parents d7cc1aa + a68ac11 commit 3cb5caf

File tree

9 files changed

+210
-18
lines changed

9 files changed

+210
-18
lines changed

src/i18n/locales/jp.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ const zh = {
145145
"hide files, support RegExp, one per line":"ファイルを隠す、正規表現をサポートし、1行に1つ",
146146
"Install": "インストール",
147147
"Installing": "インストール中",
148+
"Backup & Restore": "バックアップと復元",
149+
"Backup": "バックアップ",
150+
"Restore": "復元",
151+
"Virtual path": "仮想パス",
148152
}
149153

150154
export default zh

src/i18n/locales/zh.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ const zh = {
145145
"hide files, support RegExp, one per line":"隐藏文件,支持正则表达式,每行一个",
146146
"Install": "安装",
147147
"Installing": "安装中",
148+
"Backup & Restore": "备份与还原",
149+
"Backup": "备份",
150+
"Restore": "还原",
151+
"Virtual path": "虚拟路径",
148152
}
149153

150154
export default zh

src/pages/list/layout/files/index.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,19 @@ const Files = () => {
6565
/>
6666
)}
6767
<Page />
68-
<Viewer
69-
visible={visible}
70-
activeIndex={index}
71-
onClose={() => {
72-
setVisible(false);
73-
}}
74-
onChange={(_, index) => {
75-
setIndex(index);
76-
}}
77-
images={images}
78-
/>
68+
{visible && (
69+
<Viewer
70+
visible={visible}
71+
activeIndex={index}
72+
onClose={() => {
73+
setVisible(false);
74+
}}
75+
onChange={(_, index) => {
76+
setIndex(index);
77+
}}
78+
images={images}
79+
/>
80+
)}
7981
<ContextMenu />
8082
</Box>
8183
);

src/pages/manage/accounts.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import admin from "../../utils/admin";
3232
import { useTranslation } from "react-i18next";
3333
import FormItem from "../../components/form-item";
3434
import { copyToClip, readFromClip } from "../../utils/copy-clip";
35-
interface Account {
35+
export interface Account {
3636
id: number;
3737
name: string;
3838
type: string;
@@ -98,11 +98,11 @@ interface PropItem {
9898
}
9999

100100
function GetDefaultValue(
101-
type: "string" | "bool" | "select" | "number",
101+
type: "string" | "bool" | "select" | "number" | "text",
102102
value?: string
103103
) {
104104
switch (type) {
105-
case "string":
105+
case "string" || "text":
106106
if (value) {
107107
return value;
108108
}
@@ -128,7 +128,7 @@ function GetDefaultValue(
128128
const CommonItems: PropItem[] = [
129129
{
130130
name: "name",
131-
label: "name",
131+
label: "Virtual path",
132132
type: "string",
133133
required: true,
134134
},
@@ -235,7 +235,7 @@ const Accounts = () => {
235235
<Table w="full">
236236
<Thead>
237237
<Tr>
238-
<Th>{t("name")}</Th>
238+
<Th>{t("Virtual path")}</Th>
239239
<Th>{t("type")}</Th>
240240
<Th>{t("root_folder")}</Th>
241241
<Th>{t("index")}</Th>
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { Box, Button, Input, Textarea, useToast } from "@chakra-ui/react";
2+
import React from "react";
3+
import { useTranslation } from "react-i18next";
4+
import admin from "~/utils/admin";
5+
import download from "~/utils/download-json";
6+
import { Account } from "./accounts";
7+
import { Meta } from "./metas";
8+
import { SettingItem } from "./settings";
9+
10+
interface Data {
11+
accounts: Account[];
12+
settings: SettingItem[];
13+
metas: Meta[];
14+
}
15+
16+
const NO_BACKUP_SETTINGS = ["version", "password"];
17+
18+
const data: Data = {
19+
accounts: [],
20+
settings: [],
21+
metas: [],
22+
};
23+
24+
const BackupRestore = () => {
25+
const { t } = useTranslation();
26+
const [log, setLog] = React.useState("");
27+
const addLog = (msg: string) => {
28+
setLog((log) => `${log}\n${msg}`);
29+
};
30+
const toast = useToast();
31+
const fail = (msg: string) => {
32+
toast({
33+
title: t(msg),
34+
status: "error",
35+
duration: 3000,
36+
isClosable: true,
37+
});
38+
};
39+
const backup = async () => {
40+
setLog("get settings...");
41+
const resp1 = await admin.get("settings");
42+
const res1 = resp1.data;
43+
if (res1.code !== 200) {
44+
fail(res1.message);
45+
return;
46+
} else {
47+
addLog("get settings success");
48+
data.settings = res1.data;
49+
data.settings = data.settings.filter(
50+
(item) => !NO_BACKUP_SETTINGS.includes(item.key)
51+
);
52+
}
53+
addLog("get accounts...");
54+
const resp2 = await admin.get("accounts");
55+
const res2 = resp2.data;
56+
if (res2.code !== 200) {
57+
fail(res2.message);
58+
return;
59+
} else {
60+
addLog("get accounts success");
61+
data.accounts = res2.data;
62+
// data.accounts = data.accounts.map((account) => {
63+
// account.id = 0;
64+
// return account;
65+
// });
66+
}
67+
addLog("get metas...");
68+
const resp3 = await admin.get("metas");
69+
const res3 = resp3.data;
70+
if (res3.code !== 200) {
71+
fail(res3.message);
72+
return;
73+
} else {
74+
addLog("get metas success");
75+
data.metas = res3.data;
76+
}
77+
addLog("download backup file...");
78+
download(
79+
`${
80+
data.settings.find((item) => item.key === "title")?.value || "alist"
81+
}.json`,
82+
data
83+
);
84+
};
85+
const restore = async () => {
86+
setLog("choose data file...");
87+
const fileInput = document.querySelector(
88+
"#restore-file"
89+
) as HTMLInputElement;
90+
fileInput.click();
91+
};
92+
const onRestoreFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
93+
const files = e.target.files;
94+
const file = files![0];
95+
if (!files || !file) {
96+
return;
97+
}
98+
const reader = new FileReader();
99+
reader.onload = async (e) => {
100+
const data: Data = JSON.parse(reader.result as string);
101+
addLog("restore settings...");
102+
const resp1 = await admin.post("settings", data.settings);
103+
const res1 = resp1.data;
104+
if (res1.code !== 200) {
105+
fail(res1.message);
106+
return;
107+
}
108+
addLog("restore settings success");
109+
addLog("restore accounts...");
110+
for (const account of data.accounts) {
111+
const resp2 = await admin.post("account/create", { ...account, id: 0 });
112+
const res2 = resp2.data;
113+
if (res2.code !== 200) {
114+
addLog(
115+
`failed to restore account:[${account.name}],the reason is [${res2.message}]`
116+
);
117+
continue;
118+
}
119+
addLog(`restore account:[${account.name}] success`);
120+
}
121+
addLog("finish restore accounts");
122+
addLog("restore metas...");
123+
for (const meta of data.metas) {
124+
const resp3 = await admin.post("meta/create", { ...meta, id: 0 });
125+
const res3 = resp3.data;
126+
if (res3.code !== 200) {
127+
addLog(
128+
`failed to restore meta:[${meta.path}],the reason is [${res3.message}]`
129+
);
130+
continue;
131+
}
132+
addLog(`restore meta:[${meta.path}] success`);
133+
}
134+
addLog("finish restore metas");
135+
toast({
136+
title: t("restore success"),
137+
status: "success",
138+
duration: 3000,
139+
isClosable: true,
140+
});
141+
};
142+
reader.readAsText(file);
143+
};
144+
return (
145+
<Box>
146+
<Button colorScheme="green" onClick={backup}>
147+
{t("Backup")}
148+
</Button>
149+
<Button ml="2" onClick={restore}>
150+
{t("Restore")}
151+
</Button>
152+
<Input
153+
display="none"
154+
type="file"
155+
id="restore-file"
156+
onChange={onRestoreFileChange}
157+
/>
158+
<Textarea readOnly rows={23} mt="2" value={log}></Textarea>
159+
</Box>
160+
);
161+
};
162+
163+
export default BackupRestore;

src/pages/manage/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import admin, { changeToken } from "../../utils/admin";
3535
import Overlay from "../../components/overlay";
3636
import useTitle from "../../hooks/useTitle";
3737
import { IconType } from "react-icons";
38+
import {FaDatabase} from "react-icons/fa";
3839

3940
const Login = lazy(() => import("./login"));
4041
const Settings = lazy(() => import("./settings"));
@@ -80,6 +81,12 @@ const NavItems: NavItem[] = [
8081
icon: SiMetabase,
8182
component: Metas,
8283
},
84+
{
85+
name: "Backup & Restore",
86+
to: "backup-restore",
87+
icon: FaDatabase,
88+
component: lazy(() => import("./backup-restore")),
89+
}
8390
];
8491

8592
const getAllNavItems = (items: NavItem[], acc: NavItem[] = []) => {

src/pages/manage/metas.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { useTranslation } from "react-i18next";
3232
import admin from "../../utils/admin";
3333
import FormItem from "../../components/form-item";
3434

35-
interface Meta {
35+
export interface Meta {
3636
id: number;
3737
path: string;
3838
password: string;

src/pages/manage/settings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next";
1515
import FormItem from "../../components/form-item";
1616
import { useLocation } from "react-router-dom";
1717

18-
interface SettingItem {
18+
export interface SettingItem {
1919
key: string;
2020
value: string;
2121
description: string;

src/utils/download-json.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
function download(filename: string, data: any) {
2+
const blob = new Blob([JSON.stringify(data,null,2)], {
3+
type: "application/json",
4+
});
5+
const url = URL.createObjectURL(blob);
6+
const a = document.createElement("a");
7+
a.href = url;
8+
a.download = filename;
9+
a.click();
10+
URL.revokeObjectURL(url);
11+
}
12+
export default download;

0 commit comments

Comments
 (0)