feat: data management

This commit is contained in:
Tristan Yang
2023-05-30 17:41:08 +08:00
parent 1209e9d095
commit 5c686c7e42
19 changed files with 274 additions and 12 deletions
+2 -1
View File
@@ -61,5 +61,6 @@
"block": "Block", "block": "Block",
"unblock": "Unblock", "unblock": "Unblock",
"contact_tip": "This user is not in your contact", "contact_tip": "This user is not in your contact",
"contact_block_tip": "This user has been blocked by you" "contact_block_tip": "This user has been blocked by you",
"file_expired": "File Expired"
} }
+24
View File
@@ -7,6 +7,7 @@
"general": "General", "general": "General",
"overview": "Overview", "overview": "Overview",
"members": "Members", "members": "Members",
"data_management": "Data Management",
"auto_delete_msg": "Auto-delete Messages", "auto_delete_msg": "Auto-delete Messages",
"user": "User", "user": "User",
"my_account": "My Account", "my_account": "My Account",
@@ -95,6 +96,29 @@
"disable": "Disable" "disable": "Disable"
} }
}, },
"data": {
"clear_files": {
"title": "Files and Images Data",
"desc": "Click the button below to remove all files and images data of this server.",
"btn": "Clear Files Data"
},
"clear_msgs": {
"title": "All Messages Data",
"desc": "Click the button below to remove all the chat data including texts and files of this server.",
"btn": "Clear Chats Data"
},
"auto_delete_file": {
"title": "Auto-delete File Data",
"desc": "Set expire time for all file data of this server.",
"confirm_desc": "All files older than the set time will be deleted.",
"off": "Off",
"day1": "1 Day",
"day7": "7 Days",
"day30": "30 Days",
"day90": "90 Days",
"day180": "180 Days"
}
},
"license": { "license": {
"signed": "Signed", "signed": "Signed",
"domain": "Domains", "domain": "Domains",
+2 -1
View File
@@ -61,5 +61,6 @@
"block": "屏蔽", "block": "屏蔽",
"unblock": "解除屏蔽", "unblock": "解除屏蔽",
"contact_tip": "该用户不在您的联系人列表中", "contact_tip": "该用户不在您的联系人列表中",
"contact_block_tip": "该用户已被你屏蔽" "contact_block_tip": "该用户已被你屏蔽",
"file_expired": "文件已过期"
} }
+24
View File
@@ -7,6 +7,7 @@
"general": "通用", "general": "通用",
"overview": "概况", "overview": "概况",
"members": "成员", "members": "成员",
"data_management": "数据管理",
"auto_delete_msg": "自动删除消息", "auto_delete_msg": "自动删除消息",
"my_account": "我的账号", "my_account": "我的账号",
"config": "配置", "config": "配置",
@@ -89,6 +90,29 @@
"disable": "关闭" "disable": "关闭"
} }
}, },
"data": {
"clear_files": {
"title": "文件数据",
"desc": "清除所有人的文件图片类型数据",
"btn": "一键删除"
},
"clear_msgs": {
"title": "聊天数据",
"desc": "清除所有人的聊天数据,即所有文字与文件数据。",
"btn": "一键删除"
},
"auto_delete_file": {
"title": "定时删除数据",
"desc": "请选择定时消息的时长",
"confirm_desc": "所有在此之前的文件数据都将删除",
"off": "关闭",
"day1": "1天",
"day7": "7天",
"day30": "30天",
"day90": "90天",
"day180": "180天"
}
},
"license": { "license": {
"signed": "签名", "signed": "签名",
"domain": "域名", "domain": "域名",
+15 -1
View File
@@ -365,6 +365,18 @@ export const serverApi = createApi({
body: { license } body: { license }
}) })
}), }),
clearAllMessages: builder.query<void, void>({
query: () => ({
url: "/admin/system/message/clear",
method: "DELETE"
})
}),
clearAllFiles: builder.query<void, void>({
query: () => ({
url: "/resource/file/delete",
method: "DELETE"
})
}),
getBotRelatedChannels: builder.query<Channel[], { api_key: string; public_only?: boolean }>({ getBotRelatedChannels: builder.query<Channel[], { api_key: string; public_only?: boolean }>({
query: ({ api_key, public_only = false }) => ({ query: ({ api_key, public_only = false }) => ({
url: public_only ? `/bot?public_only=${public_only}` : `/bot`, url: public_only ? `/bot?public_only=${public_only}` : `/bot`,
@@ -443,5 +455,7 @@ export const {
useLazyGetSystemCommonQuery, useLazyGetSystemCommonQuery,
useGetSystemCommonQuery, useGetSystemCommonQuery,
useGenerateAgoraTokenMutation, useGenerateAgoraTokenMutation,
useLazyGetAgoraUsersByChannelQuery useLazyGetAgoraUsersByChannelQuery,
useLazyClearAllFilesQuery,
useLazyClearAllMessagesQuery
} = serverApi; } = serverApi;
@@ -0,0 +1,76 @@
// import React from 'react'
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useGetSystemCommonQuery, useUpdateSystemCommonMutation } from "@/app/services/server";
import { useAppSelector } from "@/app/store";
import { MessageExpireMode } from "@/types/server";
import Modal from "@/components/Modal";
import SettingBlock from "@/components/SettingBlock";
import Button from "@/components/styled/Button";
import StyledModal from "@/components/styled/Modal";
import StyledRadio from "@/components/styled/Radio";
// type Props = {
// updateConfirmModal: (context: VisibleModalType | null) => void;
// };
const AutoDeleteFiles = () => {
const currStatus = useAppSelector((store) => store.server.max_file_expiry_mode ?? "Off");
const { t } = useTranslation("setting", { keyPrefix: "data.auto_delete_file" });
const { t: ct } = useTranslation();
const { refetch } = useGetSystemCommonQuery();
const [selected, setSelected] = useState(currStatus);
const [updateSetting, { isSuccess, isLoading }] = useUpdateSystemCommonMutation();
useEffect(() => {
if (isSuccess) {
refetch();
toast.success(ct("tip.update"));
}
}, [isSuccess]);
const handleChange = (newVal: MessageExpireMode) => {
// updateConfirmModal("auto_delete_file");
setSelected(newVal);
};
const handleClose = () => {
setSelected(currStatus);
};
const handleUpdate = () => {
// todo
updateSetting({ max_file_expiry_mode: selected });
};
// if (!loadSuccess) return null;
return (
<>
<SettingBlock title={t("title")} desc={t("desc")}>
<StyledRadio
options={[t("off"), t("day1"), t("day7"), t("day30"), t("day90"), t("day180")]}
values={["Off", "Day1", "Day7", "Day30", "Day90", "Day180"]}
value={currStatus}
onChange={handleChange}
/>
</SettingBlock>
{selected !== currStatus && (
<Modal id="modal-modal">
<StyledModal
title={"Are you sure?"}
description={selected == "Off" ? "" : t("confirm_desc")}
buttons={
<>
<Button className="cancel" onClick={handleClose}>
{ct("action.cancel")}
</Button>
<Button onClick={handleUpdate} className="danger">
{isLoading ? "Updating" : ct("action.yes")}
</Button>
</>
}
></StyledModal>
</Modal>
)}
</>
);
};
export default AutoDeleteFiles;
@@ -0,0 +1,66 @@
import { FC, useEffect } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { useLazyClearAllFilesQuery, useLazyClearAllMessagesQuery } from "@/app/services/server";
import Modal from "@/components/Modal";
import Button from "@/components/styled/Button";
import StyledModal from "@/components/styled/Modal";
import { VisibleModalType } from "./index";
interface Props {
context: VisibleModalType;
title: string;
desc: string;
closeModal: () => void;
}
const ClearConfirmModal: FC<Props> = ({ context, title, desc, closeModal }) => {
// const { t } = useTranslation("auth");
const { t: ct } = useTranslation();
const [clearFiles, { isLoading: filesClearing, isSuccess: clearFilesSuccess }] =
useLazyClearAllFilesQuery();
const [clearMessages, { isLoading: msgClearing, isSuccess: clearMsgSuccess }] =
useLazyClearAllMessagesQuery();
const handleClear = () => {
//todo
switch (context) {
case "chat":
clearMessages();
break;
case "files":
clearFiles();
break;
default:
break;
}
};
const clearSuccess = clearFilesSuccess || clearMsgSuccess;
useEffect(() => {
if (clearSuccess) {
toast.success("Clear success");
closeModal();
}
}, [clearSuccess]);
const clearing = msgClearing || filesClearing;
return (
<Modal id="modal-modal">
<StyledModal
title={title}
description={desc}
buttons={
<>
<Button className="cancel" onClick={closeModal}>
{ct("action.cancel")}
</Button>
<Button onClick={handleClear} className="danger">
{clearing ? "Clearing" : ct("action.remove")}
</Button>
</>
}
></StyledModal>
</Modal>
);
};
export default ClearConfirmModal;
@@ -0,0 +1,49 @@
// import { useState, useEffect, ChangeEvent } from "react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
// import { useAppSelector } from "@/app/store";
import SettingBlock from "@/components/SettingBlock";
import StyledButton from "@/components/styled/Button";
import AutoDeleteFiles from "./AutoDeleteFiles";
import ClearConfirmModal from "./ClearConfirmModal";
export type VisibleModalType = "chat" | "files";
export default function DataManagement() {
const [visibleModal, setVisibleModal] = useState<VisibleModalType | null>(null);
const { t } = useTranslation("setting");
const handleModalVisible = (visible: VisibleModalType | null) => {
setVisibleModal(visible);
};
const clearConfirmDesc = {
chat: t("data.clear_msgs.desc"),
files: t("data.clear_files.desc")
};
return (
<div className="relative w-full md:w-[512px] flex flex-col gap-6">
{/* 清除服务器聊天消息 */}
<SettingBlock title={t("data.clear_msgs.title")} desc={t("data.clear_msgs.desc")}>
<StyledButton onClick={handleModalVisible.bind(null, "chat")} className="danger">
{t("data.clear_msgs.btn")}
</StyledButton>
</SettingBlock>
{/* 清除服务器文件 */}
<SettingBlock title={t("data.clear_files.title")} desc={t("data.clear_files.desc")}>
<StyledButton onClick={handleModalVisible.bind(null, "files")} className="danger">
{t("data.clear_files.btn")}
</StyledButton>
</SettingBlock>
{/* 自动清除清除服务器数据 */}
<AutoDeleteFiles />
{visibleModal && (
<ClearConfirmModal
closeModal={handleModalVisible.bind(null, null)}
context={visibleModal}
title="Are you sure?"
desc={clearConfirmDesc[visibleModal]}
/>
)}
</div>
);
}
+1 -1
View File
@@ -3,6 +3,7 @@ import { useEffect } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio"; import StyledRadio from "@/components/styled/Radio";
import { import {
useGetSystemCommonQuery, useGetSystemCommonQuery,
@@ -10,7 +11,6 @@ import {
} from "../../../app/services/server"; } from "../../../app/services/server";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import { ChatLayout } from "../../../types/server"; import { ChatLayout } from "../../../types/server";
import SettingBlock from "./SettingBlock";
// type Props = {} // type Props = {}
@@ -3,13 +3,13 @@ import { useEffect } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio"; import StyledRadio from "@/components/styled/Radio";
import { import {
useGetSystemCommonQuery, useGetSystemCommonQuery,
useUpdateSystemCommonMutation useUpdateSystemCommonMutation
} from "../../../app/services/server"; } from "../../../app/services/server";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import SettingBlock from "./SettingBlock";
// type Props = {} // type Props = {}
+1 -1
View File
@@ -1,9 +1,9 @@
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import Radio from "../../../components/styled/Radio"; import Radio from "../../../components/styled/Radio";
import { Theme } from "../../../types/common"; import { Theme } from "../../../types/common";
import SettingBlock from "./SettingBlock";
// type Props = {} // type Props = {}
+1 -1
View File
@@ -3,10 +3,10 @@ import { ChangeEvent, useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import StyledButton from "@/components/styled/Button"; import StyledButton from "@/components/styled/Button";
import StyledInput from "@/components/styled/Input"; import StyledInput from "@/components/styled/Input";
import { useGetFrontendUrlQuery, useUpdateFrontendUrlMutation } from "../../../app/services/server"; import { useGetFrontendUrlQuery, useUpdateFrontendUrlMutation } from "../../../app/services/server";
import SettingBlock from "./SettingBlock";
// type Props = {} // type Props = {}
+1 -1
View File
@@ -1,8 +1,8 @@
// import React from 'react' // import React from 'react'
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio"; import StyledRadio from "@/components/styled/Radio";
import SettingBlock from "./SettingBlock";
// type Props = {} // type Props = {}
+1 -1
View File
@@ -3,13 +3,13 @@ import { useEffect } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio"; import StyledRadio from "@/components/styled/Radio";
import { import {
useGetSystemCommonQuery, useGetSystemCommonQuery,
useUpdateSystemCommonMutation useUpdateSystemCommonMutation
} from "../../../app/services/server"; } from "../../../app/services/server";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import SettingBlock from "./SettingBlock";
// type Props = {} // type Props = {}
+1 -1
View File
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { LoginConfig, WhoCanSignUp } from "@/types/server"; import { LoginConfig, WhoCanSignUp } from "@/types/server";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio"; import StyledRadio from "@/components/styled/Radio";
import useConfig from "@/hooks/useConfig"; import useConfig from "@/hooks/useConfig";
import ChatLayout from "./ChatLayout"; import ChatLayout from "./ChatLayout";
@@ -12,7 +13,6 @@ import FrontendURL from "./FrontendURL";
import Language from "./Language"; import Language from "./Language";
import OnlineStatus from "./OnlineStatus"; import OnlineStatus from "./OnlineStatus";
import Server from "./Server"; import Server from "./Server";
import SettingBlock from "./SettingBlock";
export default function Overview() { export default function Overview() {
const { t } = useTranslation("setting"); const { t } = useTranslation("setting");
+1 -1
View File
@@ -1,8 +1,8 @@
// import React from 'react' // import React from 'react'
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SettingBlock from "@/components/SettingBlock";
import StyledRadio from "@/components/styled/Radio"; import StyledRadio from "@/components/styled/Radio";
import SettingBlock from "./SettingBlock";
// type Props = {} // type Props = {}
+6
View File
@@ -10,6 +10,7 @@ import ConfigAgora from "./config/Agora";
import ConfigFirebase from "./config/Firebase"; import ConfigFirebase from "./config/Firebase";
import Logins from "./config/Logins"; import Logins from "./config/Logins";
import ConfigSMTP from "./config/SMTP"; import ConfigSMTP from "./config/SMTP";
import DataManagement from "./DataManagement";
import License from "./License"; import License from "./License";
import MyAccount from "./MyAccount"; import MyAccount from "./MyAccount";
import Overview from "./Overview"; import Overview from "./Overview";
@@ -31,6 +32,11 @@ const navs = [
name: "members", name: "members",
component: <ManageMembers />, component: <ManageMembers />,
admin: true admin: true
},
{
name: "data_management",
component: <DataManagement />,
admin: true
} }
] ]
}, },
+2 -1
View File
@@ -7,12 +7,13 @@ export interface Server extends SystemCommon {
description: string; description: string;
} }
export type ChatLayout = "SelfRight" | "Left"; export type ChatLayout = "SelfRight" | "Left";
export type MessageExpireMode = "Off" | "Day1" | "Day7" | "Day30" | "Day90" | "Day180";
export interface SystemCommon { export interface SystemCommon {
show_user_online_status?: boolean; show_user_online_status?: boolean;
webclient_auto_update?: boolean; webclient_auto_update?: boolean;
contact_verification_enable?: boolean; contact_verification_enable?: boolean;
chat_layout_mode?: ChatLayout; chat_layout_mode?: ChatLayout;
max_file_expiry_mode?: "Off" | "Day1" | "Day7" | "Day30" | "Day90" | "Day180"; max_file_expiry_mode?: MessageExpireMode;
} }
export interface GithubAuthConfig { export interface GithubAuthConfig {
client_id?: string; client_id?: string;