feat: bot setting
This commit is contained in:
@@ -106,7 +106,7 @@ export const userApi = createApi({
|
||||
url: `admin/user/bot-api-key/${uid}`,
|
||||
method: "POST",
|
||||
body: { name },
|
||||
responseHandler: (response: Response) => response.text()
|
||||
// responseHandler: (response: Response) => response.text()
|
||||
}),
|
||||
|
||||
}),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FC, memo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import IconOwner from "../../../assets/icons/owner.svg";
|
||||
import IconBot from "../../../assets/icons/bot.svg";
|
||||
import Avatar from "../Avatar";
|
||||
import Profile from "../Profile";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
@@ -63,6 +64,7 @@ const User: FC<Props> = ({
|
||||
</div>
|
||||
{!compact && <span className="name" title={curr?.name}>{curr?.name}</span>}
|
||||
{owner && <IconOwner />}
|
||||
{curr.is_bot && <IconBot className="!w-5 !h-5" />}
|
||||
</StyledWrapper>
|
||||
</ContextMenu>
|
||||
);
|
||||
@@ -93,6 +95,7 @@ const User: FC<Props> = ({
|
||||
</div>
|
||||
{!compact && <span className="name" title={curr?.name}>{curr?.name}</span>}
|
||||
{owner && <IconOwner />}
|
||||
{curr.is_bot && <IconBot className="!w-5 !h-5" />}
|
||||
</StyledWrapper>
|
||||
</Tippy>
|
||||
</ContextMenu>
|
||||
|
||||
@@ -4,8 +4,10 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetBotAPIKeysQuery } from '../../../app/services/user';
|
||||
import IconDelete from '../../../assets/icons/delete.svg';
|
||||
import IconAdd from '../../../assets/icons/add.svg';
|
||||
import CreateAPIKeyModal from './CreateAPIKeyModal';
|
||||
import DeleteAPIKeyModal from './DeleteAPIKeyModal';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type Props = {
|
||||
uid: number
|
||||
@@ -34,22 +36,20 @@ const BotAPIKeys = ({ uid }: Props) => {
|
||||
setDeleteApiKey(param);
|
||||
};
|
||||
if (!data) return null;
|
||||
const colWidths = ["w-20", "w-[166px]", "w-36", "w-15", "w-10"];
|
||||
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'>
|
||||
<div className='border-t border-solid border-b border-gray-100 py-2 w-full'>
|
||||
<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">
|
||||
{[t("col_key_name"), t('col_key_value'), t('col_key_create_time'), t('col_key_last_used'), ""].map((title, idx) => <th key={title} scope="col" className={clsx(`text-xs text-gray-900 px-1 text-left pb-2`, colWidths[idx])}>
|
||||
{title}
|
||||
</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map(ak => {
|
||||
{data.length > 0 ? data.map(ak => {
|
||||
const { id, name, key, created_at, last_used } = ak;
|
||||
return <tr key={id} className="group" >
|
||||
<td className={tdClass}>
|
||||
@@ -62,7 +62,6 @@ const BotAPIKeys = ({ uid }: Props) => {
|
||||
{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`}>
|
||||
@@ -71,9 +70,17 @@ const BotAPIKeys = ({ uid }: Props) => {
|
||||
</button>
|
||||
</td>
|
||||
</tr>;
|
||||
})}
|
||||
}) : <tr>
|
||||
<td colSpan={5} className='w-full text-center text-xs text-gray-400 pt-4'>
|
||||
No API Key Yet
|
||||
</td>
|
||||
</tr>}
|
||||
</tbody>
|
||||
</table></div> : null}
|
||||
</table>
|
||||
<button onClick={toggleCreateModal.bind(null, uid)} className="text-green-600 text-xs py-0.5 flex items-center gap-1 m-auto my-2 bg-green-50 rounded-full px-2 ">
|
||||
<IconAdd className="!w-4 !h-4 fill-green-600" /> Add API Key
|
||||
</button>
|
||||
</div>
|
||||
{currentUid && <CreateAPIKeyModal uid={currentUid} closeModal={toggleCreateModal.bind(null, undefined)} />}
|
||||
{deleteApiKey && <DeleteAPIKeyModal uid={deleteApiKey.uid} kid={deleteApiKey.kid} closeModal={toggleDeleteModal.bind(null, undefined)} />}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ type Props = {
|
||||
}
|
||||
const CreateAPIKeyModal = ({ closeModal, uid }: Props) => {
|
||||
const { copy } = useCopy();
|
||||
const [createBotAPIKey, { error, isSuccess, isLoading, data }] = useCreateBotAPIKeyMutation();
|
||||
const [createBotAPIKey, { error, isSuccess, isLoading, data = "" }] = useCreateBotAPIKeyMutation();
|
||||
const formRef = useRef(null);
|
||||
const { t } = useTranslation("setting", { keyPrefix: "bot" });
|
||||
const { t: ct } = useTranslation();
|
||||
@@ -25,7 +25,6 @@ const CreateAPIKeyModal = ({ closeModal, uid }: Props) => {
|
||||
if (!formEle.checkValidity()) {
|
||||
formEle.reportValidity();
|
||||
return;
|
||||
|
||||
}
|
||||
createBotAPIKey({
|
||||
uid,
|
||||
|
||||
@@ -73,7 +73,7 @@ const CreateModal = ({ closeModal }: Props) => {
|
||||
<Button className="cancel" onClick={closeModal}>
|
||||
{ct("action.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleCreateBot}>{isLoading ? "Updating" : ct("action.done")}</Button>
|
||||
<Button onClick={handleCreateBot}>{isLoading ? "Creating" : ct("action.done")}</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
@@ -83,7 +83,7 @@ const CreateModal = ({ closeModal }: Props) => {
|
||||
<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>
|
||||
<label htmlFor={"webhook_url"} className="text-sm text-[#6b7280]">Webhook URL (Optional)</label>
|
||||
<Input name={"webhook_url"} type="url" placeholder='Please input webhook url'></Input>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -58,22 +58,30 @@ const WebhookEdit = ({ uid }: Props) => {
|
||||
};
|
||||
const handleCancelEdit = () => {
|
||||
setEditable(false);
|
||||
const form = formRef.current;
|
||||
if (form) {
|
||||
const input = form.querySelector("input");
|
||||
input!.value = data?.webhook_url || "";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{(url || editable || updateSuccess) ?
|
||||
<div className="flex gap-2">
|
||||
<form action="/" ref={formRef} >
|
||||
<form action="/" ref={formRef} onSubmit={(evt) => {
|
||||
evt.preventDefault();
|
||||
handleEdit();
|
||||
}}>
|
||||
<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 />
|
||||
<IconSave className="stroke-gray-500 !w-5 !h-5" />
|
||||
|
||||
: <IconEdit />}
|
||||
: <IconEdit className="fill-gray-500 !w-5 !h-5" />}
|
||||
</button>
|
||||
{editable && !isUpdating && <button type='button' disabled={isUpdating} onClick={handleCancelEdit}>
|
||||
<IconCancel className="!w-6 !h-6" />
|
||||
<IconCancel className="!w-5 !h-5 fill-gray-500" />
|
||||
</button>}
|
||||
</div>
|
||||
:
|
||||
|
||||
@@ -22,7 +22,7 @@ const Tip = ({ title, desc }: TipProps) => {
|
||||
<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";
|
||||
const tdClass = "p-6 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() {
|
||||
@@ -75,16 +75,19 @@ export default function BotConfig() {
|
||||
{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">
|
||||
<td className="px-4 py-2">
|
||||
<AvatarUploader uid={uid} url={avatar} uploadImage={updateAvatar} name={name} className="!w-14 !h-14" />
|
||||
<span className='text-xs text-gray-500'>
|
||||
|
||||
</td>
|
||||
<td className={`${tdClass}`}>
|
||||
<div>
|
||||
{name}
|
||||
</div>
|
||||
<div className='text-xs text-gray-500'>
|
||||
#{uid}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
{name}
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
<td className={`${tdClass} py-2`}>
|
||||
<BotAPIKeys uid={uid} />
|
||||
</td>
|
||||
<td className={tdClass}>
|
||||
|
||||
Reference in New Issue
Block a user