feat: bot setting

This commit is contained in:
Tristan Yang
2022-12-19 22:07:04 +08:00
parent e297411d00
commit 2b2cdf4df8
18 changed files with 934 additions and 497 deletions
+22 -2
View File
@@ -6,7 +6,7 @@ import { fillUsers } from "../slices/users";
import BASE_URL, { ContentTypes } from "../config";
import { onMessageSendStarted } from "./handlers";
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
import { AutoDeleteMsgDTO, User, UserCreateDTO, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user";
import { AutoDeleteMsgDTO, BotAPIKey, User, UserCreateDTO, UserDTO, UserForAdmin, UserForAdminDTO } from "../../types/user";
import { ChatMessage, ContentTypeKey, MuteDTO } from "../../types/message";
import { RootState } from "../store";
@@ -100,6 +100,23 @@ export const userApi = createApi({
getUserByAdmin: builder.query<UserForAdmin, number>({
query: (uid) => ({ url: `/admin/user/${uid}` })
}),
// bot operations
createBotAPIKey: builder.mutation<void, { uid: number, name: string }>({
query: ({ uid, name }) => ({
url: `admin/user/bot-api-key/${uid}`,
method: "POST",
body: { name },
responseHandler: (response: Response) => response.text()
}),
}),
getBotAPIKeys: builder.query<BotAPIKey[], number>({
query: (uid) => ({ url: `/admin/user/bot-api-key/${uid}` })
}),
deleteBotAPIKey: builder.query<void, { uid: number, kid: number }>({
query: ({ uid, kid }) => ({ url: `/admin/user/bot-api-key/${uid}/${kid}`, method: "DELETE" })
}),
// bot operations end
updateInfo: builder.mutation<User, UserDTO>({
query: (data) => ({
url: `user`,
@@ -157,5 +174,8 @@ export const {
useUpdateAvatarMutation,
useGetUsersQuery,
useLazyGetUsersQuery,
useSendMsgMutation
useSendMsgMutation,
useCreateBotAPIKeyMutation,
useLazyDeleteBotAPIKeyQuery,
useGetBotAPIKeysQuery
} = userApi;
+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 21V13H7V21M7 3V8H15M19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H16L21 8V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21Z" stroke="#616161" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 463 B

+8 -5
View File
@@ -54,9 +54,12 @@ export default function useStreaming() {
window.clearTimeout(aliveInter);
// 比15秒多5秒
aliveInter = window.setTimeout(() => {
// 重启连接
stopStreaming();
startStreaming();
// 有网的情况再试
if (navigator.onLine) {
// 重启连接
stopStreaming();
startStreaming();
}
}, timeout ?? 20000);
};
const startStreaming = useCallback(
@@ -114,7 +117,7 @@ export default function useStreaming() {
return;
}
// 重连
connectionIsOpen = false;
// connectionIsOpen = false;
keepAlive(2000);
};
SSE.onmessage = (evt) => {
@@ -306,8 +309,8 @@ export default function useStreaming() {
if (SSE) {
SSE.close();
SSE = undefined;
connectionIsOpen = false;
}
connectionIsOpen = false;
};
useEffect(() => {
@@ -0,0 +1,83 @@
// import Tippy from '@tippyjs/react';
import dayjs from 'dayjs';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetBotAPIKeysQuery } from '../../../app/services/user';
import IconDelete from '../../../assets/icons/delete.svg';
import CreateAPIKeyModal from './CreateAPIKeyModal';
import DeleteAPIKeyModal from './DeleteAPIKeyModal';
type Props = {
uid: number
}
// const APIKeyTable = () => {
// return <table>
// </table>;
// };
type DeleteAPIKeyProps = { uid: number, kid: number }
const tdClass = "p-1 whitespace-nowrap text-xs text-gray-500 align-middle px-1";
const BotAPIKeys = ({ uid }: Props) => {
const { t } = useTranslation("setting", { keyPrefix: "bot" });
const [currentUid, setCurrentUid] = useState<number | undefined>();
const [deleteApiKey, setDeleteApiKey] = useState<DeleteAPIKeyProps | undefined>();
const { data, refetch } = useGetBotAPIKeysQuery(uid);
const toggleCreateModal = (param?: number) => {
if (!param) refetch();
setCurrentUid(param);
};
const toggleDeleteModal = (param?: DeleteAPIKeyProps) => {
if (!param) refetch();
setDeleteApiKey(param);
};
if (!data) return null;
return (
<div className='flex flex-col gap-2 items-start'>
<button onClick={toggleCreateModal.bind(null, uid)} type='button' className="rounded-full bg-green-50 text-green-600 text-xs py-0.5 px-2">
New API Key
</button>
{data.length > 0 ? <div className='border-t border-solid border-b border-gray-200 py-2'>
<table className="min-w-full table-auto font-mono">
<thead >
<tr >
{[t("col_key_name"), t('col_key_value'), t('col_key_create_time'), t('col_key_last_used'), ""].map(title => <th key={title} scope="col" className="text-xs text-gray-900 px-1 text-left pb-2">
{title}
</th>)}
</tr>
</thead>
<tbody>
{data.map(ak => {
const { id, name, key, created_at, last_used } = ak;
return <tr key={id} className="group" >
<td className={tdClass}>
{name}
</td>
<td className={`${tdClass} w-40`}>
{`${key.slice(0, 4)} ... ... ${key.slice(-6)}`}
</td>
<td className={tdClass}>
{dayjs(created_at).format("YYYY-MM-DD HH:mm:ss")}
</td>
<td className={tdClass}>
{last_used ? dayjs(last_used).format("YYYY-MM-DD HH:mm:ss") : "Unused"}
</td>
<td className={`${tdClass} invisible group-hover:visible`}>
<button onClick={toggleDeleteModal.bind(null, { kid: id, uid })}>
<IconDelete />
</button>
</td>
</tr>;
})}
</tbody>
</table></div> : null}
{currentUid && <CreateAPIKeyModal uid={currentUid} closeModal={toggleCreateModal.bind(null, undefined)} />}
{deleteApiKey && <DeleteAPIKeyModal uid={deleteApiKey.uid} kid={deleteApiKey.kid} closeModal={toggleDeleteModal.bind(null, undefined)} />}
</div>
);
};
export default BotAPIKeys;
@@ -0,0 +1,87 @@
import { useRef, useEffect } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useCreateBotAPIKeyMutation } from '../../../app/services/user';
import Modal from '../../../common/component/Modal';
import Button from '../../../common/component/styled/Button';
import Input from '../../../common/component/styled/Input';
import StyledModal from '../../../common/component/styled/Modal';
import useCopy from '../../../common/hook/useCopy';
type Props = {
uid: number,
closeModal: () => void
}
const CreateAPIKeyModal = ({ closeModal, uid }: Props) => {
const { copy } = useCopy();
const [createBotAPIKey, { error, isSuccess, isLoading, data }] = useCreateBotAPIKeyMutation();
const formRef = useRef(null);
const { t } = useTranslation("setting", { keyPrefix: "bot" });
const { t: ct } = useTranslation();
// const [input, setInput] = useState("");
const handleCreateBot = () => {
if (!formRef || !formRef.current) return;
const formEle = formRef.current as HTMLFormElement;
if (!formEle.checkValidity()) {
formEle.reportValidity();
return;
}
createBotAPIKey({
uid,
name: formEle.querySelector('input')?.value || ""
});
};
useEffect(() => {
if (error) {
switch (error.status) {
case 406:
toast.error("Invalid Webhook URL!");
break;
default:
break;
}
}
}, [error]);
const handleCopy = () => {
copy(data);
toast.success("API Key Copied!");
closeModal();
};
return (
<Modal id="modal-modal">
<StyledModal
title={t("create_key_title")}
description={t("create_key_desc")}
buttons={
isSuccess ? <Button onClick={handleCopy}>
{t("key_copy_and_close")}
</Button> : <>
<Button className="cancel" onClick={closeModal}>
{ct("action.cancel")}
</Button>
<Button onClick={handleCreateBot}>{isLoading ? "..." : ct("action.done")}</Button>
</>
}
>
{isSuccess ? <div className="flex flex-col gap-2 text-sm">
<div className='border-green-600 bg-green-200/50 rounded border border-solid p-2 max-w-md w-full whitespace-pre-wrap break-all' >
{data}
</div> <div className='text-red-400'>
{t("create_key_warning")}
</div>
</div>
: <form ref={formRef} className="w-full flex flex-col gap-2 items-center" action="/">
<div className="flex flex-col gap-1 w-full">
<label htmlFor={"name"} className="text-sm text-[#6b7280]">Name</label>
<Input name={"name"} required placeholder='Please input API Key name'></Input>
</div>
</form>}
</StyledModal>
</Modal>
);
};
export default CreateAPIKeyModal;
@@ -0,0 +1,95 @@
import { useRef, useState, useEffect } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useCreateUserMutation } from '../../../app/services/user';
import Modal from '../../../common/component/Modal';
import Button from '../../../common/component/styled/Button';
import Input from '../../../common/component/styled/Input';
import StyledModal from '../../../common/component/styled/Modal';
type Props = {
closeModal: () => void
}
type CreateDTO = {
name: string,
webhook_url?: string
}
const CreateModal = ({ closeModal }: Props) => {
const [createUser, { isSuccess, isLoading, error }] = useCreateUserMutation();
const formRef = useRef(null);
const { t } = useTranslation("setting", { keyPrefix: "bot" });
const { t: ct } = useTranslation();
// const [input, setInput] = useState("");
const handleCreateBot = () => {
if (!formRef || !formRef.current) return;
const formEle = formRef.current as HTMLFormElement;
if (!formEle.checkValidity()) {
formEle.reportValidity();
return;
}
const myFormData = new FormData(formEle);
const formDataObj: CreateDTO = { name: "" };
myFormData.forEach((value, key) => {
if (value) {
formDataObj[key] = value;
}
});
createUser({
is_bot: true,
is_admin: false,
gender: 1,
email: `bot_${new Date().getTime()}@voce.chat`,
password: "",
...formDataObj
});
};
useEffect(() => {
if (error) {
switch (error.status) {
case 406:
toast.error("Invalid Webhook URL!");
break;
default:
break;
}
}
}, [error]);
useEffect(() => {
if (isSuccess) {
toast.success("Create Bot Successfully!");
closeModal();
}
}, [isSuccess]);
return (
<Modal id="modal-modal">
<StyledModal
title={t("create_title")}
description={t("create_desc")}
buttons={
<>
<Button className="cancel" onClick={closeModal}>
{ct("action.cancel")}
</Button>
<Button onClick={handleCreateBot}>{isLoading ? "Updating" : ct("action.done")}</Button>
</>
}
>
<form ref={formRef} className="w-full flex flex-col gap-2 items-center" action="/">
<div className="flex flex-col gap-1 w-full">
<label htmlFor={"name"} className="text-sm text-[#6b7280]">Name</label>
<Input name={"name"} required placeholder='Please input bot name'></Input>
</div>
<div className="flex flex-col gap-1 w-full">
<label htmlFor={"webhook_url"} className="text-sm text-[#6b7280]">Webhook URL</label>
<Input name={"webhook_url"} type="url" placeholder='Please input webhook url'></Input>
</div>
</form>
</StyledModal>
</Modal>
);
};
export default CreateModal;
@@ -0,0 +1,48 @@
import { useEffect } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useLazyDeleteBotAPIKeyQuery } from '../../../app/services/user';
import Modal from '../../../common/component/Modal';
import Button from '../../../common/component/styled/Button';
import StyledModal from '../../../common/component/styled/Modal';
type Props = {
uid: number,
kid: number,
closeModal: () => void
}
const DeleteAPIKeyModal = ({ closeModal, uid, kid }: Props) => {
const [deleteKey, { isSuccess, isLoading }] = useLazyDeleteBotAPIKeyQuery();
const { t } = useTranslation("setting", { keyPrefix: "bot" });
const { t: ct } = useTranslation();
// const [input, setInput] = useState("");
const handleDeleteBot = () => {
deleteKey({ uid, kid });
};
useEffect(() => {
if (isSuccess) {
toast.success("Delete API Key Successfully!");
closeModal();
}
}, [isSuccess]);
return (
<Modal id="modal-modal">
<StyledModal
title={`${t("delete_key_title")} ${name}`}
description={t("delete_key_desc")}
buttons={
<>
<Button className="cancel" onClick={closeModal}>
{ct("action.cancel")}
</Button>
<Button className='danger' onClick={handleDeleteBot}>{isLoading ? "Deleting" : ct("action.done")}</Button>
</>
}
>
</StyledModal>
</Modal>
);
};
export default DeleteAPIKeyModal;
@@ -0,0 +1,48 @@
import { useEffect } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useLazyDeleteUserQuery } from '../../../app/services/user';
import Modal from '../../../common/component/Modal';
import Button from '../../../common/component/styled/Button';
import StyledModal from '../../../common/component/styled/Modal';
type Props = {
uid: number,
name: string,
closeModal: () => void
}
const DeleteModal = ({ closeModal, uid, name }: Props) => {
const [deleteUser, { isSuccess, isLoading }] = useLazyDeleteUserQuery();
const { t } = useTranslation("setting", { keyPrefix: "bot" });
const { t: ct } = useTranslation();
// const [input, setInput] = useState("");
const handleDeleteBot = () => {
deleteUser(uid);
};
useEffect(() => {
if (isSuccess) {
toast.success("Delete Bot Successfully!");
closeModal();
}
}, [isSuccess]);
return (
<Modal id="modal-modal">
<StyledModal
title={`${t("delete_title")} ${name}`}
description={t("delete_desc")}
buttons={
<>
<Button className="cancel" onClick={closeModal.bind(null, undefined)}>
{ct("action.cancel")}
</Button>
<Button className='danger' onClick={handleDeleteBot}>{isLoading ? "Deleting" : ct("action.done")}</Button>
</>
}
>
</StyledModal>
</Modal>
);
};
export default DeleteModal;
@@ -0,0 +1,87 @@
import clsx from 'clsx';
import { useState, useEffect, useRef } from 'react';
import { toast } from 'react-hot-toast';
import { Orbit } from "@uiball/loaders";
import { useGetUserByAdminQuery, useUpdateUserMutation } from '../../../app/services/user';
import IconEdit from '../../../assets/icons/edit.svg';
import IconSave from '../../../assets/icons/save.svg';
import IconCancel from '../../../assets/icons/close.circle.svg';
type Props = {
uid: number
}
const WebhookEdit = ({ uid }: Props) => {
const formRef = useRef<HTMLFormElement>(null);
const [editable, setEditable] = useState(false);
const [url, setUrl] = useState("");
const { data, isSuccess, refetch } = useGetUserByAdminQuery(uid);
const [updateUser, { isSuccess: updateSuccess, isLoading: isUpdating }] = useUpdateUserMutation();
useEffect(() => {
if (isSuccess && data) {
setUrl(data.webhook_url || "");
}
}, [data, isSuccess]);
useEffect(() => {
if (updateSuccess) {
refetch();
}
}, [updateSuccess]);
const handleEdit = async () => {
if (editable && formRef) {
const form = formRef.current;
// 检查格式
if (!form?.checkValidity()) {
form?.reportValidity();
return;
}
// 保存编辑
const webhook_url = new FormData(form).get("webhook") as string;
const resp = await updateUser({ id: uid, webhook_url });
console.log("ressssss", resp);
if ("error" in resp) {
switch (resp.error.status) {
case 406:
toast.error("Not Valid URL!");
break;
default:
break;
}
return;
}
}
setEditable(prev => !prev);
};
const handleEditable = () => {
setEditable(true);
};
const handleCancelEdit = () => {
setEditable(false);
};
return (
<div>
{(url || editable || updateSuccess) ?
<div className="flex gap-2">
<form action="/" ref={formRef} >
<input readOnly={!editable} required autoFocus type="url" name='webhook' defaultValue={url} className={clsx("text-sm text-gray-500 px-2 py-1", editable ? "border border-solid border-gray-200 bg-gray-50" : "bg-transparent")} />
</form>
<button type='button' disabled={isUpdating} onClick={handleEdit}>
{isUpdating ? <Orbit size={16} /> : editable ?
<IconSave />
: <IconEdit />}
</button>
{editable && !isUpdating && <button type='button' disabled={isUpdating} onClick={handleCancelEdit}>
<IconCancel className="!w-6 !h-6" />
</button>}
</div>
:
<button type='button' className="rounded-full bg-primary-50 text-green-600 text-xs py-0.5 px-2" onClick={handleEditable}>
Set Webhook
</button>}
</div>
);
};
export default WebhookEdit;
@@ -0,0 +1,69 @@
import { useRef, useState, useEffect, ChangeEvent } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useUpdateUserMutation } from '../../../app/services/user';
import Modal from '../../../common/component/Modal';
import Button from '../../../common/component/styled/Button';
import Input from '../../../common/component/styled/Input';
import StyledModal from '../../../common/component/styled/Modal';
type Props = {
uid: number,
webhook?: string,
closeModal: () => void
}
const WebhookModal = ({ uid, webhook, closeModal }: Props) => {
const [url, setUrl] = useState(webhook);
const [updateUser, { isSuccess, isLoading }] = useUpdateUserMutation();
const formRef = useRef(null);
const { t } = useTranslation("setting", { keyPrefix: "bot" });
const { t: ct } = useTranslation();
// const [input, setInput] = useState("");
const handleUrlChange = (evt: ChangeEvent<HTMLInputElement>) => {
setUrl(evt.target.value);
};
const handleUpdateWebhook = () => {
if (!formRef || !formRef.current) return;
const formEle = formRef.current as HTMLFormElement;
if (!formEle.checkValidity()) {
formEle.reportValidity();
return;
}
const myFormData = new FormData(formEle);
const webhook_url = myFormData.get("webhook")?.toString() || "";
updateUser({
id: uid,
webhook_url
});
};
useEffect(() => {
if (isSuccess) {
toast.success("Update Webhook URL Successfully!");
closeModal();
}
}, [isSuccess]);
return (
<Modal id="modal-modal">
<StyledModal
title={t("webhook_title")}
description={t("webhook_desc")}
buttons={
<>
<Button className="cancel" onClick={closeModal.bind(null, undefined)}>
{ct("action.cancel")}
</Button>
<Button disabled={!url} onClick={handleUpdateWebhook}>{isLoading ? "Updating" : ct("action.done")}</Button>
</>
}
>
<form ref={formRef} className="w-full flex flex-col gap-2" action="/">
<label htmlFor={"webhook"} className="text-sm text-[#6b7280]">Webhook URL</label>
<Input name={"webhook"} value={url} onChange={handleUrlChange} type="url"></Input>
</form>
</StyledModal>
</Modal>
);
};
export default WebhookModal;
+112
View File
@@ -0,0 +1,112 @@
import { useEffect } from "react";
// import dayjs from "dayjs";
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useUpdateAvatarByAdminMutation } from '../../../app/services/user';
import { useAppSelector } from '../../../app/store';
import AvatarUploader from '../../../common/component/AvatarUploader';
import Button from '../../../common/component/styled/Button';
import IconDelete from '../../../assets/icons/delete.svg';
import CreateModal from './CreateModal';
import WebhookEdit from './WebhookEdit';
import WebhookModal from './WebhookModal';
import DeleteModal from './DeleteModal';
import BotAPIKeys from './BotAPIKeys';
import { toast } from 'react-hot-toast';
type TipProps = { title: string, desc: string };
const Tip = ({ title, desc }: TipProps) => {
return <div className="flex flex-col text-sm">
<h2 className="">{title}</h2>
<p className="text-gray-500">{desc}</p>
</div>;
};
const tdClass = "px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 align-top";
type WebhookParams = { webhook?: string, uid: number };
type DeleteParams = { name: string, uid: number };
export default function BotConfig() {
const [updateAvatar, { isSuccess: updateAvatarSuccess }] = useUpdateAvatarByAdminMutation();
const [createModalVisible, setCreateModalVisible] = useState(false);
const [currWebhookParams, setCurrWebhookParams] = useState<WebhookParams | undefined>(undefined);
const [currDeleteParams, setCurrDeleteParams] = useState<DeleteParams | undefined>(undefined);
const bots = useAppSelector(store => Object.values(store.users.byId).filter(u => !!u.is_bot));
const { t } = useTranslation("setting", { keyPrefix: "bot" });
const toggleCreateModalVisible = () => {
setCreateModalVisible(prev => !prev);
};
const toggleWebhookModalVisible = (obj?: WebhookParams) => {
console.log("webhook modal", obj);
setCurrWebhookParams(obj);
};
const toggleDeleteModalVisible = (obj?: DeleteParams) => {
setCurrDeleteParams(obj);
};
useEffect(() => {
if (updateAvatarSuccess) {
toast.success("Update Bot Avatar Successfully!");
}
}, [updateAvatarSuccess]);
return (
<>
<div className="flex flex-col justify-start items-start gap-4">
<div className="flex flex-col gap-4 max-w-[634px] mb-4">
<Tip title={t("bot_tip_title")} desc={t("bot_tip_desc")} />
<Tip title={t("webhook_tip_title")} desc={t("webhook_tip_desc")} />
</div>
<div className="flex flex-col gap-1">
<h2 className='font-semibold'>{t("manage")}</h2>
<p className='text-gray-500 text-xs'>{t("manage_desc")}</p>
</div>
<div className="w-fit overflow-hidden">
<table className="min-w-full table-auto">
<thead className="border-b bg-gray-50">
<tr>
{[t("col_avatar"), t('col_name'), t('col_api_key'), t('col_webhook'), t('col_opt')].map(title => <th key={title} scope="col" className="text-sm font-bold text-gray-900 px-6 py-4 text-left">
{title}
</th>)}
</tr>
</thead>
<tbody>
{bots.map(bot => {
const { uid, name, avatar } = bot;
return <tr key={uid} className="bg-white border-b transition duration-300 ease-in-out hover:bg-gray-100">
<td className="px-4 py-2 flex flex-col gap-1 items-start">
<AvatarUploader uid={uid} url={avatar} uploadImage={updateAvatar} name={name} className="!w-14 !h-14" />
<span className='text-xs text-gray-500'>
#{uid}
</span>
</td>
<td className={tdClass}>
{name}
</td>
<td className={tdClass}>
<BotAPIKeys uid={uid} />
</td>
<td className={tdClass}>
<WebhookEdit uid={uid} />
</td>
<td className={tdClass}>
<button type='button' onClick={toggleDeleteModalVisible.bind(null, { uid, name })} >
<IconDelete className="hover:opacity-80" />
</button>
{/* <Button className='mini' onClick={toggleWebhookModalVisible.bind(null, { webhook: webhook_url, uid })} >Set Webhook</Button> */}
</td>
</tr>;
})}
</tbody>
</table>
</div>
<Button onClick={toggleCreateModalVisible} className="ghost small">Add</Button>
</div>
{createModalVisible && <CreateModal closeModal={toggleCreateModalVisible} />}
{currWebhookParams && <WebhookModal closeModal={toggleWebhookModalVisible} {...currWebhookParams} />}
{currDeleteParams && <DeleteModal closeModal={toggleDeleteModalVisible} {...currDeleteParams} />}
</>
);
}
+6
View File
@@ -6,6 +6,7 @@ import ConfigSMTP from "./config/SMTP";
import APIConfig from "./APIConfig";
import License from "./License/License";
import Widget from "./Widget";
import BotConfig from "./BotConfig";
import APIDocument from "./APIDocument";
import ManageMembers from "../../common/component/ManageMembers";
import FAQ from "../../common/component/FAQ";
@@ -35,6 +36,11 @@ const navs = [
{
title: "config",
items: [
{
name: "bot",
component: <BotConfig />,
admin: true
},
{
name: "firebase",
component: <ConfigFirebase />
+20 -2
View File
@@ -1,14 +1,20 @@
export type Gender = 0 | 1;
export interface AutoDeleteMsgDTO {
users?: { uid: number, expires_in: number }[],
groups?: { gid: number, expires_in: number }[],
}
export interface User {
uid: number;
email: string;
name: string;
gender: Gender;
language: string;
language?: string;
is_admin: boolean;
avatar_updated_at: number;
create_by: string;
webhook_url?: string;
is_bot?: boolean;
}
export type UserStatus = "normal" | "frozen";
export type UserDevice = {
@@ -16,6 +22,13 @@ export type UserDevice = {
device_token?: string;
is_online: boolean;
};
export type BotAPIKey = {
id: number,
name: string,
key: string,
created_at: number,
last_used: number
};
export interface UserForAdmin extends User {
password: string;
@@ -27,4 +40,9 @@ export interface UserForAdmin extends User {
export interface UserForAdminDTO extends Partial<UserForAdmin> {
id?: number;
}
export interface UserDTO extends Partial<Pick<User, "name" | "gender" | "language" | "email">> {}
export interface UserDTO extends Partial<Pick<User, "name" | "gender" | "language" | "email" | "webhook_url">> {
password?: string
}
export interface UserCreateDTO extends Pick<User, "name" | "gender" | "language" | "email" | "webhook_url" | "is_bot" | "is_admin"> {
password: string;
}