📦 chore(frontend): add moment package as a dependency to handle date and time formatting

🐛 fix(headerComponent): add onClick event handler to navigate to "/account/api-keys" when button is clicked

🐛 fix(API): add missing API functions for getting, creating, and deleting API keys

🐛 fix(SecretKeyModal): add functionality to create a new API key and set the value in the input field

🔧 fix(ApiKeysPage): remove unused imports and refactor code to improve readability and performance
✨ feat(ApiKeysPage): add support for fetching and displaying API keys for the logged-in user
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
🔧 fix(ApiKeysPage): refactor code to improve readability and remove unnecessary functions and variables
This commit is contained in:
Cristhian Zanforlin Lousa 2023-08-16 18:41:40 -03:00
commit ad8cbddd91
7 changed files with 120 additions and 175 deletions

View file

@ -45,6 +45,7 @@
"esbuild": "^0.17.18", "esbuild": "^0.17.18",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.233.0", "lucide-react": "^0.233.0",
"moment": "^2.29.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",
@ -7726,6 +7727,14 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": {
"node": "*"
}
},
"node_modules/mri": { "node_modules/mri": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",

View file

@ -40,6 +40,7 @@
"esbuild": "^0.17.18", "esbuild": "^0.17.18",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.233.0", "lucide-react": "^0.233.0",
"moment": "^2.29.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-cookie": "^4.1.1", "react-cookie": "^4.1.1",

View file

@ -146,7 +146,11 @@ export default function Header() {
/> />
</div> </div>
</AlertDropdown> </AlertDropdown>
<button> <button
onClick={() => {
navigate("/account/api-keys");
}}
>
<IconComponent <IconComponent
name="Key" name="Key"
className="side-bar-button-size text-muted-foreground hover:text-accent-foreground" className="side-bar-button-size text-muted-foreground hover:text-accent-foreground"

View file

@ -467,3 +467,41 @@ export async function updateUser(user_id: string, user: Users) {
throw error; throw error;
} }
} }
export async function getApiKey(user_id: String) {
try {
const res = await api.get(`${BASE_URL_API}api_key/${user_id}`);
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
export async function createApiKey(user_id: string) {
try {
const res = await api.post(`${BASE_URL_API}api_key/${user_id}`);
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
export async function deleteApiKey(user_id: string) {
try {
const res = await api.delete(`${BASE_URL_API}api_key/${user_id}`);
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}

View file

@ -5,6 +5,7 @@ import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
import { CONTROL_NEW_API_KEY } from "../../constants/constants"; import { CONTROL_NEW_API_KEY } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { createApiKey } from "../../controllers/API";
import { import {
ApiKeyInputType, ApiKeyInputType,
ApiKeyType, ApiKeyType,
@ -20,13 +21,11 @@ export default function SecretKeyModal({
children, children,
icon, icon,
data, data,
index,
onConfirm,
}: ApiKeyType) { }: ApiKeyType) {
const Icon: any = nodeIconsLucide[icon]; const Icon: any = nodeIconsLucide[icon];
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? ""); const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
const [apiKeyValue, setApiKeyValue] = useState("Value"); const [apiKeyValue, setApiKeyValue] = useState("");
const [inputState, setInputState] = const [inputState, setInputState] =
useState<ApiKeyInputType>(CONTROL_NEW_API_KEY); useState<ApiKeyInputType>(CONTROL_NEW_API_KEY);
const [renderKey, setRenderKey] = useState(false); const [renderKey, setRenderKey] = useState(false);
@ -49,7 +48,7 @@ export default function SecretKeyModal({
function resetForm() { function resetForm() {
setApiKeyName(""); setApiKeyName("");
setApiKeyValue("Value"); setApiKeyValue("");
} }
const handleCopyClick = async () => { const handleCopyClick = async () => {
@ -68,6 +67,14 @@ export default function SecretKeyModal({
} }
}; };
function handleAddNewKey() {
createApiKey(data)
.then((res) => {
setApiKeyValue(res["api_key"]);
})
.catch((err) => {});
}
return ( return (
<BaseModal size="small-h-full" open={open} setOpen={setOpen}> <BaseModal size="small-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger>{children}</BaseModal.Trigger>
@ -122,6 +129,7 @@ export default function SecretKeyModal({
<Form.Root <Form.Root
onSubmit={(event) => { onSubmit={(event) => {
setRenderKey(true); setRenderKey(true);
handleAddNewKey();
event.preventDefault(); event.preventDefault();
}} }}
> >

View file

@ -1,4 +1,3 @@
import { cloneDeep } from "lodash";
import { useContext, useEffect, useRef, useState } from "react"; import { useContext, useEffect, useRef, useState } from "react";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
@ -13,165 +12,55 @@ import {
} from "../../components/ui/table"; } from "../../components/ui/table";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { import { deleteApiKey, getApiKey } from "../../controllers/API";
addUser,
deleteUser,
getUsersPage,
updateUser,
} from "../../controllers/API";
import ConfirmationModal from "../../modals/ConfirmationModal"; import ConfirmationModal from "../../modals/ConfirmationModal";
import SecretKeyModal from "../../modals/SecretKeyModal"; import SecretKeyModal from "../../modals/SecretKeyModal";
import { UserInputType } from "../../types/components";
import moment from "moment";
export default function ApiKeysPage() { export default function ApiKeysPage() {
const [inputValue, setInputValue] = useState(""); const [loadingKeys, setLoadingKeys] = useState(true);
const [size, setPageSize] = useState(10);
const [index, setPageIndex] = useState(0);
const [loadingUsers, setLoadingUsers] = useState(true);
const { setErrorData, setSuccessData } = useContext(alertContext); const { setErrorData, setSuccessData } = useContext(alertContext);
const { userData } = useContext(AuthContext); const { userData } = useContext(AuthContext);
const [totalRowsCount, setTotalRowsCount] = useState(0); const [userId, setUserId] = useState("");
const keysList = useRef([]);
const userList = useRef([]);
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
getUsers(); getKeys();
}, 500); }, 500);
}, []); }, [userData]);
const [filterUserList, setFilterUserList] = useState(userList.current); function getKeys() {
setLoadingKeys(true);
function getUsers() { if (userData) {
setLoadingUsers(true); getApiKey(userData.id)
getUsersPage(index, size) .then((keys) => {
.then((users) => { keysList.current = keys["api_keys"];
setTotalRowsCount(users["total_count"]); setUserId(keys["user_id"]);
userList.current = users["users"]; setLoadingKeys(false);
setFilterUserList(users["users"]); })
setLoadingUsers(false); .catch((error) => {
}) setLoadingKeys(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) { function resetFilter() {
deleteUser(user.id) getKeys();
.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) { function handleDeleteKey(keys) {
updateUser(userId, user) deleteApiKey(keys)
.then((res) => { .then((res) => {
resetFilter(); resetFilter();
setSuccessData({ setSuccessData({
title: "Success! User edited!", title: "Success! Key deleted!",
}); });
}) })
.catch((error) => { .catch((error) => {
setErrorData({ setErrorData({
title: "Error on edit user", title: "Error on delete key",
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"]], list: [error["response"]["data"]["detail"]],
}); });
}); });
@ -188,6 +77,12 @@ export default function ApiKeysPage() {
); );
} }
function getIdKeyHidden(apiKey: string) {
const firstTwoChars = apiKey.slice(0, 2);
const lastFourChars = apiKey.slice(-4);
return firstTwoChars + "..." + lastFourChars;
}
return ( return (
<> <>
{userData && ( {userData && (
@ -211,7 +106,7 @@ export default function ApiKeysPage() {
<div className="flex items-center space-x-2"></div> <div className="flex items-center space-x-2"></div>
</div> </div>
{userList.current.length === 0 && !loadingUsers && ( {keysList.current.length === 0 && !loadingKeys && (
<> <>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2>There's no users registered :)</h2> <h2>There's no users registered :)</h2>
@ -219,21 +114,21 @@ export default function ApiKeysPage() {
</> </>
)} )}
<> <>
{loadingUsers && ( {loadingKeys && (
<div> <div>
<strong>Loading...</strong> <strong>Loading...</strong>
</div> </div>
)} )}
<div <div
className={ className={
"max-h-[15rem] min-h-[15rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" + "max-h-[15rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
(loadingUsers ? " border-0" : "") (loadingKeys ? " border-0" : "")
} }
> >
<Table className={"table-fixed bg-muted outline-1"}> <Table className={"table-fixed bg-muted outline-1"}>
<TableHeader <TableHeader
className={ className={
loadingUsers loadingKeys
? "hidden" ? "hidden"
: "table-fixed bg-muted outline-1" : "table-fixed bg-muted outline-1"
} }
@ -256,37 +151,31 @@ export default function ApiKeysPage() {
<TableHead className="h-10 w-[100px] text-right"></TableHead> <TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
{!loadingUsers && ( {!loadingKeys && (
<TableBody> <TableBody>
{filterUserList.map((user, index) => ( {keysList.current.map((api_keys, index) => (
<TableRow key={index}> <TableRow key={index}>
<TableCell className="truncate py-2"> <TableCell className="truncate py-2">
<ShadTooltip content={user.id}> <ShadTooltip content={api_keys.name}>
<span className="cursor-default"> <span className="cursor-default">
{user.id} {api_keys.name}
</span> </span>
</ShadTooltip> </ShadTooltip>
</TableCell> </TableCell>
<TableCell className="truncate py-2"> <TableCell className="truncate py-2">
<ShadTooltip content={user.username}> <span className="cursor-default">
<span className="cursor-default"> {getIdKeyHidden(api_keys.id)}
{user.username} </span>
</span>
</ShadTooltip>
</TableCell> </TableCell>
<TableCell className="truncate py-2 "> <TableCell className="truncate py-2 ">
{ {moment(api_keys.create_at).format(
new Date(user.create_at) "YYYY-MM-DD HH:mm"
.toISOString() )}
.split("T")[0]
}
</TableCell> </TableCell>
<TableCell className="truncate py-2"> <TableCell className="truncate py-2">
{ {moment(api_keys.last_used_at).format(
new Date(user.updated_at) "YYYY-MM-DD HH:mm"
.toISOString() )}
.split("T")[0]
}
</TableCell> </TableCell>
<TableCell className="flex w-[100px] py-2 text-right"> <TableCell className="flex w-[100px] py-2 text-right">
<div className="flex"> <div className="flex">
@ -294,14 +183,14 @@ export default function ApiKeysPage() {
title="Delete" title="Delete"
titleHeader="Delete User" titleHeader="Delete User"
modalContentTitle="Attention!" modalContentTitle="Attention!"
modalContent="Are you sure you want to delete this user? This action cannot be undone." modalContent="Are you sure you want to delete this key? This action cannot be undone."
cancelText="Cancel" cancelText="Cancel"
confirmationText="Delete" confirmationText="Delete"
icon={"UserMinus2"} icon={"UserMinus2"}
data={user} data={api_keys.id}
index={index} index={index}
onConfirm={(index, user) => { onConfirm={(index, keys) => {
handleDeleteUser(user); handleDeleteKey(keys);
}} }}
> >
<ShadTooltip content="Delete" side="top"> <ShadTooltip content="Delete" side="top">
@ -327,9 +216,7 @@ export default function ApiKeysPage() {
cancelText="Cancel" cancelText="Cancel"
confirmationText="Create secret key" confirmationText="Create secret key"
icon={"Key"} icon={"Key"}
onConfirm={(index, user) => { data={userId}
handleNewUser(user);
}}
> >
<Button> <Button>
<IconComponent name="Plus" className="mr-1 h-5 w-5" /> <IconComponent name="Plus" className="mr-1 h-5 w-5" />

View file

@ -241,8 +241,6 @@ export type ApiKeyType = {
children: ReactElement; children: ReactElement;
icon: string; icon: string;
data?: any; data?: any;
index?: number;
onConfirm: (index, data) => void;
}; };
export type ApiKeyInputType = { export type ApiKeyInputType = {