🐛 fix(App.tsx): remove unnecessary code block in useEffect
🔧 chore(constants.ts): add CONTROL_NEW_API_KEY constant for consistency ✨ feat(SecretKeyModal): add SecretKeyModal component to handle secret key generation and copying 🔧 chore(UserManagementModal): rearrange buttons in UserManagementModal for better user experience 🚀 feat(ApiKeysPage): add new page for managing API keys 🔧 chore(routes.tsx): add route for ApiKeysPage 🔧 chore(types): add ApiKeyType and ApiKeyInputType to improve type safety and readability of code 🔧 chore(utils): add Key icon from lucide-react to nodeIconsLucide to be used in styling
This commit is contained in:
parent
ca52a61012
commit
ba48bbe770
8 changed files with 578 additions and 8 deletions
|
|
@ -150,9 +150,6 @@ export default function App() {
|
||||||
})
|
})
|
||||||
.catch((error) => {});
|
.catch((error) => {});
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
navigate("/login");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -528,6 +528,10 @@ export const CONTROL_NEW_USER = {
|
||||||
is_superuser: false,
|
is_superuser: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CONTROL_NEW_API_KEY = {
|
||||||
|
apikeyname: "",
|
||||||
|
};
|
||||||
|
|
||||||
export const tabsCode = [];
|
export const tabsCode = [];
|
||||||
|
|
||||||
export function tabsArray(codes: string[], method: number) {
|
export function tabsArray(codes: string[], method: number) {
|
||||||
|
|
|
||||||
191
src/frontend/src/modals/SecretKeyModal/index.tsx
Normal file
191
src/frontend/src/modals/SecretKeyModal/index.tsx
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
import * as Form from "@radix-ui/react-form";
|
||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
import { Button } from "../../components/ui/button";
|
||||||
|
import { Input } from "../../components/ui/input";
|
||||||
|
import { CONTROL_NEW_API_KEY } from "../../constants/constants";
|
||||||
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
import {
|
||||||
|
ApiKeyInputType,
|
||||||
|
ApiKeyType,
|
||||||
|
inputHandlerEventType,
|
||||||
|
} from "../../types/components";
|
||||||
|
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
|
export default function SecretKeyModal({
|
||||||
|
title,
|
||||||
|
cancelText,
|
||||||
|
confirmationText,
|
||||||
|
children,
|
||||||
|
icon,
|
||||||
|
data,
|
||||||
|
index,
|
||||||
|
onConfirm,
|
||||||
|
}: ApiKeyType) {
|
||||||
|
const Icon: any = nodeIconsLucide[icon];
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
|
||||||
|
const [apiKeyValue, setApiKeyValue] = useState("Value");
|
||||||
|
const [inputState, setInputState] =
|
||||||
|
useState<ApiKeyInputType>(CONTROL_NEW_API_KEY);
|
||||||
|
const [renderKey, setRenderKey] = useState(false);
|
||||||
|
const [textCopied, setTextCopied] = useState(true);
|
||||||
|
const { setSuccessData } = useContext(alertContext);
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
|
function handleInput({
|
||||||
|
target: { name, value },
|
||||||
|
}: inputHandlerEventType): void {
|
||||||
|
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setRenderKey(false);
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
setApiKeyName("");
|
||||||
|
setApiKeyValue("Value");
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopyClick = async () => {
|
||||||
|
if (apiKeyValue) {
|
||||||
|
await navigator.clipboard.writeText(apiKeyValue);
|
||||||
|
inputRef.current.focus();
|
||||||
|
inputRef.current.select();
|
||||||
|
setSuccessData({
|
||||||
|
title: "API Key copied!",
|
||||||
|
});
|
||||||
|
setTextCopied(false);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setTextCopied(true);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseModal size="small-h-full" open={open} setOpen={setOpen}>
|
||||||
|
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||||
|
<BaseModal.Header description={""}>
|
||||||
|
<span className="pr-2">{title}</span>
|
||||||
|
<Icon
|
||||||
|
name="icon"
|
||||||
|
className="h-6 w-6 pl-1 text-foreground"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</BaseModal.Header>
|
||||||
|
<BaseModal.Content>
|
||||||
|
{renderKey === true && (
|
||||||
|
<>
|
||||||
|
<span className="text-xs">
|
||||||
|
Please save this secret key somewhere safe and accessible. For
|
||||||
|
security reasons,{" "}
|
||||||
|
<strong>you won't be able to view it again</strong> through your
|
||||||
|
account. If you lose this secret key, you'll need to generate a
|
||||||
|
new one.
|
||||||
|
</span>
|
||||||
|
<div className="flex pt-3">
|
||||||
|
<div className="w-full">
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
onChange={(event) => {
|
||||||
|
setApiKeyValue(event.target.value);
|
||||||
|
}}
|
||||||
|
readOnly={true}
|
||||||
|
value={apiKeyValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
className="ml-3"
|
||||||
|
onClick={() => {
|
||||||
|
handleCopyClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{textCopied ? (
|
||||||
|
<IconComponent name="Copy" className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<IconComponent name="Check" className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Root
|
||||||
|
onSubmit={(event) => {
|
||||||
|
setRenderKey(true);
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderKey === false && (
|
||||||
|
<div className="grid gap-5">
|
||||||
|
<Form.Field name="username">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "baseline",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Label className="data-[invalid]:label-invalid">
|
||||||
|
Name (optional){" "}
|
||||||
|
</Form.Label>
|
||||||
|
</div>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<input
|
||||||
|
onChange={({ target: { value } }) => {
|
||||||
|
handleInput({ target: { name: "apikeyname", value } });
|
||||||
|
setApiKeyName(value);
|
||||||
|
}}
|
||||||
|
value={apiKeyName}
|
||||||
|
className="primary-input"
|
||||||
|
placeholder="My key name"
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</Form.Field>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{renderKey === false && (
|
||||||
|
<div className="float-right">
|
||||||
|
<Button
|
||||||
|
className="mr-3"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cancelText}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Form.Submit asChild>
|
||||||
|
<Button className="mt-8">{confirmationText}</Button>
|
||||||
|
</Form.Submit>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{renderKey === true && (
|
||||||
|
<div className="float-right">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setRenderKey(false);
|
||||||
|
}}
|
||||||
|
className="mt-8"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form.Root>
|
||||||
|
</BaseModal.Content>
|
||||||
|
</BaseModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -273,17 +273,20 @@ export default function UserManagementModal({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="float-right">
|
<div className="float-right">
|
||||||
<Form.Submit asChild>
|
|
||||||
<Button className="mr-3 mt-8">{confirmationText}</Button>
|
|
||||||
</Form.Submit>
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
|
className="mr-3"
|
||||||
>
|
>
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Form.Submit asChild>
|
||||||
|
<Button className="mt-8">{confirmationText}</Button>
|
||||||
|
</Form.Submit>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Form.Root>
|
</Form.Root>
|
||||||
</BaseModal.Content>
|
</BaseModal.Content>
|
||||||
|
|
|
||||||
349
src/frontend/src/pages/ApiKeysPage/index.tsx
Normal file
349
src/frontend/src/pages/ApiKeysPage/index.tsx
Normal file
|
|
@ -0,0 +1,349 @@
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
import { Button } from "../../components/ui/button";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "../../components/ui/table";
|
||||||
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
import {
|
||||||
|
addUser,
|
||||||
|
deleteUser,
|
||||||
|
getUsersPage,
|
||||||
|
updateUser,
|
||||||
|
} from "../../controllers/API";
|
||||||
|
import ConfirmationModal from "../../modals/ConfirmationModal";
|
||||||
|
import SecretKeyModal from "../../modals/SecretKeyModal";
|
||||||
|
import { UserInputType } from "../../types/components";
|
||||||
|
|
||||||
|
export default function ApiKeysPage() {
|
||||||
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
|
||||||
|
const [size, setPageSize] = useState(10);
|
||||||
|
const [index, setPageIndex] = useState(0);
|
||||||
|
const [loadingUsers, setLoadingUsers] = useState(true);
|
||||||
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
|
const { userData } = useContext(AuthContext);
|
||||||
|
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
||||||
|
|
||||||
|
const userList = useRef([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
getUsers();
|
||||||
|
}, 500);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [filterUserList, setFilterUserList] = useState(userList.current);
|
||||||
|
|
||||||
|
function getUsers() {
|
||||||
|
setLoadingUsers(true);
|
||||||
|
getUsersPage(index, size)
|
||||||
|
.then((users) => {
|
||||||
|
setTotalRowsCount(users["total_count"]);
|
||||||
|
userList.current = users["users"];
|
||||||
|
setFilterUserList(users["users"]);
|
||||||
|
setLoadingUsers(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setLoadingUsers(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChangePagination(pageIndex: number, pageSize: number) {
|
||||||
|
setLoadingUsers(true);
|
||||||
|
getUsersPage(pageIndex, pageSize)
|
||||||
|
.then((users) => {
|
||||||
|
setTotalRowsCount(users["total_count"]);
|
||||||
|
userList.current = users["users"];
|
||||||
|
setFilterUserList(users["users"]);
|
||||||
|
setLoadingUsers(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setLoadingUsers(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFilter() {
|
||||||
|
setPageIndex(0);
|
||||||
|
setPageSize(10);
|
||||||
|
getUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFilterUsers(input: string) {
|
||||||
|
setInputValue(input);
|
||||||
|
|
||||||
|
if (input === "") {
|
||||||
|
setFilterUserList(userList.current);
|
||||||
|
} else {
|
||||||
|
const filteredList = userList.current.filter((user) =>
|
||||||
|
user.username.toLowerCase().includes(input.toLowerCase())
|
||||||
|
);
|
||||||
|
setFilterUserList(filteredList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteUser(user) {
|
||||||
|
deleteUser(user.id)
|
||||||
|
.then((res) => {
|
||||||
|
resetFilter();
|
||||||
|
setSuccessData({
|
||||||
|
title: "Success! User deleted!",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setErrorData({
|
||||||
|
title: "Error on delete user",
|
||||||
|
list: [error["response"]["data"]["detail"]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditUser(userId, user) {
|
||||||
|
updateUser(userId, user)
|
||||||
|
.then((res) => {
|
||||||
|
resetFilter();
|
||||||
|
setSuccessData({
|
||||||
|
title: "Success! User edited!",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setErrorData({
|
||||||
|
title: "Error on edit user",
|
||||||
|
list: [error["response"]["data"]["detail"]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDisableUser(check, userId, user) {
|
||||||
|
const userEdit = cloneDeep(user);
|
||||||
|
userEdit.is_active = !check;
|
||||||
|
|
||||||
|
updateUser(userId, userEdit)
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
|
||||||
|
resetFilter();
|
||||||
|
setSuccessData({
|
||||||
|
title: "Success! User edited!",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setErrorData({
|
||||||
|
title: "Error on edit user",
|
||||||
|
list: [error["response"]["data"]["detail"]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSuperUserEdit(check, userId, user) {
|
||||||
|
const userEdit = cloneDeep(user);
|
||||||
|
userEdit.is_superuser = !check;
|
||||||
|
updateUser(userId, userEdit)
|
||||||
|
.then((res) => {
|
||||||
|
resetFilter();
|
||||||
|
setSuccessData({
|
||||||
|
title: "Success! User edited!",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setErrorData({
|
||||||
|
title: "Error on edit user",
|
||||||
|
list: [error["response"]["data"]["detail"]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNewUser(user: UserInputType) {
|
||||||
|
addUser(user)
|
||||||
|
.then((res) => {
|
||||||
|
resetFilter();
|
||||||
|
setSuccessData({
|
||||||
|
title: "Success! New user added!",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setErrorData({
|
||||||
|
title: "Error on add new user",
|
||||||
|
list: [error["response"]["data"]["detail"]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function lastUsedMessage() {
|
||||||
|
return (
|
||||||
|
<div className="text-xs">
|
||||||
|
<span>
|
||||||
|
The last time this key was used.<br></br> Accurate to within the hour
|
||||||
|
from the most recent usage.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{userData && (
|
||||||
|
<div className="main-page-panel">
|
||||||
|
<div className="m-auto flex h-full flex-row justify-center">
|
||||||
|
<div className="basis-5/6">
|
||||||
|
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
||||||
|
<div className="flex items-center justify-between space-y-2">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight">
|
||||||
|
API keys
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Your secret API keys are listed below. Please note that we
|
||||||
|
do not display your secret API keys again after you
|
||||||
|
generate them.<br></br>
|
||||||
|
Do not share your API key with others, or expose it in the
|
||||||
|
browser or other client-side code.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{userList.current.length === 0 && !loadingUsers && (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h2>There's no users registered :)</h2>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<>
|
||||||
|
{loadingUsers && (
|
||||||
|
<div>
|
||||||
|
<strong>Loading...</strong>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"max-h-[15rem] min-h-[15rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
|
||||||
|
(loadingUsers ? " border-0" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Table className={"table-fixed bg-muted outline-1"}>
|
||||||
|
<TableHeader
|
||||||
|
className={
|
||||||
|
loadingUsers
|
||||||
|
? "hidden"
|
||||||
|
: "table-fixed bg-muted outline-1"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="h-10">Name</TableHead>
|
||||||
|
<TableHead className="h-10">Key</TableHead>
|
||||||
|
<TableHead className="h-10">Created</TableHead>
|
||||||
|
<TableHead className="flex h-10 items-center">
|
||||||
|
Last Used
|
||||||
|
<ShadTooltip side="top" content={lastUsedMessage()}>
|
||||||
|
<div>
|
||||||
|
<IconComponent
|
||||||
|
name="Info"
|
||||||
|
className="ml-1 h-3 w-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ShadTooltip>
|
||||||
|
</TableHead>
|
||||||
|
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
{!loadingUsers && (
|
||||||
|
<TableBody>
|
||||||
|
{filterUserList.map((user, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell className="truncate py-2">
|
||||||
|
<ShadTooltip content={user.id}>
|
||||||
|
<span className="cursor-default">
|
||||||
|
{user.id}
|
||||||
|
</span>
|
||||||
|
</ShadTooltip>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="truncate py-2">
|
||||||
|
<ShadTooltip content={user.username}>
|
||||||
|
<span className="cursor-default">
|
||||||
|
{user.username}
|
||||||
|
</span>
|
||||||
|
</ShadTooltip>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="truncate py-2 ">
|
||||||
|
{
|
||||||
|
new Date(user.create_at)
|
||||||
|
.toISOString()
|
||||||
|
.split("T")[0]
|
||||||
|
}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="truncate py-2">
|
||||||
|
{
|
||||||
|
new Date(user.updated_at)
|
||||||
|
.toISOString()
|
||||||
|
.split("T")[0]
|
||||||
|
}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="flex w-[100px] py-2 text-right">
|
||||||
|
<div className="flex">
|
||||||
|
<ConfirmationModal
|
||||||
|
title="Delete"
|
||||||
|
titleHeader="Delete User"
|
||||||
|
modalContentTitle="Attention!"
|
||||||
|
modalContent="Are you sure you want to delete this user? This action cannot be undone."
|
||||||
|
cancelText="Cancel"
|
||||||
|
confirmationText="Delete"
|
||||||
|
icon={"UserMinus2"}
|
||||||
|
data={user}
|
||||||
|
index={index}
|
||||||
|
onConfirm={(index, user) => {
|
||||||
|
handleDeleteUser(user);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ShadTooltip content="Delete" side="top">
|
||||||
|
<IconComponent
|
||||||
|
name="Trash2"
|
||||||
|
className="ml-2 h-4 w-4 cursor-pointer"
|
||||||
|
/>
|
||||||
|
</ShadTooltip>
|
||||||
|
</ConfirmationModal>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<SecretKeyModal
|
||||||
|
title="Create new secret key"
|
||||||
|
cancelText="Cancel"
|
||||||
|
confirmationText="Create secret key"
|
||||||
|
icon={"Key"}
|
||||||
|
onConfirm={(index, user) => {
|
||||||
|
handleNewUser(user);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button>
|
||||||
|
<IconComponent name="Plus" className="mr-1 h-5 w-5" />
|
||||||
|
Create new secret key
|
||||||
|
</Button>
|
||||||
|
</SecretKeyModal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ import HomePage from "./pages/MainPage";
|
||||||
import DeleteAccountPage from "./pages/deleteAccountPage";
|
import DeleteAccountPage from "./pages/deleteAccountPage";
|
||||||
import LoginPage from "./pages/loginPage";
|
import LoginPage from "./pages/loginPage";
|
||||||
import SignUp from "./pages/signUpPage";
|
import SignUp from "./pages/signUpPage";
|
||||||
|
import ApiKeysPage from "./pages/ApiKeysPage";
|
||||||
|
|
||||||
const Router = () => {
|
const Router = () => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -93,6 +94,14 @@ const Router = () => {
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
></Route>
|
></Route>
|
||||||
|
<Route
|
||||||
|
path="api-keys"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<ApiKeysPage />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
></Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -233,3 +233,18 @@ export type UserInputType = {
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
is_superuser?: boolean;
|
is_superuser?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiKeyType = {
|
||||||
|
title: string;
|
||||||
|
cancelText: string;
|
||||||
|
confirmationText: string;
|
||||||
|
children: ReactElement;
|
||||||
|
icon: string;
|
||||||
|
data?: any;
|
||||||
|
index?: number;
|
||||||
|
onConfirm: (index, data) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiKeyInputType = {
|
||||||
|
apikeyname: string;
|
||||||
|
};
|
||||||
|
|
@ -72,6 +72,7 @@ import {
|
||||||
X,
|
X,
|
||||||
XCircle,
|
XCircle,
|
||||||
Zap,
|
Zap,
|
||||||
|
Key
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { FaApple, FaGithub } from "react-icons/fa";
|
import { FaApple, FaGithub } from "react-icons/fa";
|
||||||
import { AirbyteIcon } from "../icons/Airbyte";
|
import { AirbyteIcon } from "../icons/Airbyte";
|
||||||
|
|
@ -294,5 +295,6 @@ export const nodeIconsLucide = {
|
||||||
FaApple,
|
FaApple,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
Eye,
|
Eye,
|
||||||
UserCog2
|
UserCog2,
|
||||||
|
Key
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue