Added table of API Keys at settings page

This commit is contained in:
Lucas Oliveira 2024-06-04 18:13:53 -03:00
commit 24ba353a10
4 changed files with 129 additions and 121 deletions

View file

@ -23,6 +23,7 @@ export const USER_EDIT_ERROR_ALERT = "Error on edit user";
export const USER_ADD_ERROR_ALERT = "Error when adding new user"; export const USER_ADD_ERROR_ALERT = "Error when adding new user";
export const SIGNIN_ERROR_ALERT = "Error signing in"; export const SIGNIN_ERROR_ALERT = "Error signing in";
export const DEL_KEY_ERROR_ALERT = "Error on delete key"; export const DEL_KEY_ERROR_ALERT = "Error on delete key";
export const DEL_KEY_ERROR_ALERT_PLURAL = "Error on delete keys";
export const UPLOAD_ERROR_ALERT = "Error uploading file"; export const UPLOAD_ERROR_ALERT = "Error uploading file";
export const WRONG_FILE_ERROR_ALERT = "Invalid file type"; export const WRONG_FILE_ERROR_ALERT = "Invalid file type";
export const UPLOAD_ALERT_LIST = "Please upload a JSON file"; export const UPLOAD_ALERT_LIST = "Please upload a JSON file";
@ -54,6 +55,7 @@ export const USER_DEL_SUCCESS_ALERT = "Success! User deleted!";
export const USER_EDIT_SUCCESS_ALERT = "Success! User edited!"; export const USER_EDIT_SUCCESS_ALERT = "Success! User edited!";
export const USER_ADD_SUCCESS_ALERT = "Success! New user added!"; export const USER_ADD_SUCCESS_ALERT = "Success! New user added!";
export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!"; export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!";
export const DEL_KEY_SUCCESS_ALERT_PLURAL = "Success! Keys deleted!";
export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`; export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!"; export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";

View file

@ -613,11 +613,8 @@ export const FETCH_ERROR_DESCRIPION =
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. "; export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
export const API_PAGE_PARAGRAPH_1 = export const API_PAGE_PARAGRAPH =
"Your secret API keys are listed below. Please note that we do not display your secret API keys again after you generate them."; "Your secret API keys are listed below. Do not share your API key with others, or expose it in the browser or other client-side code.";
export const API_PAGE_PARAGRAPH_2 =
"Do not share your API key with others, or expose it in the browser or other client-side code.";
export const API_PAGE_USER_KEYS = export const API_PAGE_USER_KEYS =
"This user does not have any keys assigned at the moment."; "This user does not have any keys assigned at the moment.";

View file

@ -22,8 +22,7 @@ import {
DEL_KEY_SUCCESS_ALERT, DEL_KEY_SUCCESS_ALERT,
} from "../../constants/alerts_constants"; } from "../../constants/alerts_constants";
import { import {
API_PAGE_PARAGRAPH_1, API_PAGE_PARAGRAPH,
API_PAGE_PARAGRAPH_2,
API_PAGE_USER_KEYS, API_PAGE_USER_KEYS,
LAST_USED_SPAN_1, LAST_USED_SPAN_1,
LAST_USED_SPAN_2, LAST_USED_SPAN_2,
@ -128,9 +127,7 @@ export default function ApiKeysPage() {
API keys API keys
</h2> </h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
{API_PAGE_PARAGRAPH_1} {API_PAGE_PARAGRAPH}
<br />
{API_PAGE_PARAGRAPH_2}
</p> </p>
</div> </div>
<div className="flex items-center space-x-2"></div> <div className="flex items-center space-x-2"></div>

View file

@ -2,149 +2,154 @@ import IconComponent from "../../../../components/genericIconComponent";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community"; import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community";
import { useEffect, useState } from "react"; import { useContext, useEffect, useRef, useState } from "react";
import AddNewVariableButton from "../../../../components/addNewVariableButtonComponent/addNewVariableButton"; import AddNewVariableButton from "../../../../components/addNewVariableButtonComponent/addNewVariableButton";
import Dropdown from "../../../../components/dropdownComponent"; import Dropdown from "../../../../components/dropdownComponent";
import ForwardedIconComponent from "../../../../components/genericIconComponent"; import ForwardedIconComponent from "../../../../components/genericIconComponent";
import TableComponent from "../../../../components/tableComponent"; import TableComponent from "../../../../components/tableComponent";
import { Badge } from "../../../../components/ui/badge"; import { Badge } from "../../../../components/ui/badge";
import { Card, CardContent } from "../../../../components/ui/card"; import { Card, CardContent } from "../../../../components/ui/card";
import { deleteGlobalVariable } from "../../../../controllers/API"; import {
deleteApiKey,
deleteGlobalVariable,
getApiKey,
} from "../../../../controllers/API";
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
import { useGlobalVariablesStore } from "../../../../stores/globalVariablesStore/globalVariables"; import { useGlobalVariablesStore } from "../../../../stores/globalVariablesStore/globalVariables";
import { cn } from "../../../../utils/utils"; import { cn } from "../../../../utils/utils";
import {
API_PAGE_PARAGRAPH,
LAST_USED_SPAN_1,
LAST_USED_SPAN_2,
} from "../../../../constants/constants";
import TableAutoCellRender from "../../../../components/tableAutoCellRender";
import {
DEL_KEY_SUCCESS_ALERT,
DEL_KEY_ERROR_ALERT,
DEL_KEY_SUCCESS_ALERT_PLURAL,
DEL_KEY_ERROR_ALERT_PLURAL,
} from "../../../../constants/alerts_constants";
import { AuthContext } from "../../../../contexts/authContext";
import { ApiKey } from "../../../../types/components";
import SecretKeyModal from "../../../../modals/secretKeyModal";
export default function ApiKeysPage() { export default function ApiKeysPage() {
const globalVariablesEntries = useGlobalVariablesStore( const [loadingKeys, setLoadingKeys] = useState(true);
(state) => state.globalVariablesEntries, const [selectedRows, setSelectedRows] = useState<string[]>([]);
); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable,
);
const globalVariables = useGlobalVariablesStore(
(state) => state.globalVariables,
);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); const { userData } = useContext(AuthContext);
const [userId, setUserId] = useState("");
const BadgeRenderer = (props) => { const keysList = useRef([]);
return props.value !== "" ? (
<div>
<Badge variant="outline" size="md" className="font-normal">
{props.value}
</Badge>
</div>
) : (
<div></div>
);
};
const [rowData, setRowData] = useState<
{
type: string | undefined;
id: string;
name: string;
default_fields: string | undefined;
}[]
>([]);
useEffect(() => { useEffect(() => {
const rows: Array<{ getKeys();
type: string | undefined; }, [userData]);
id: string;
name: string;
default_fields: string | undefined;
}> = [];
if (globalVariablesEntries === undefined) return;
globalVariablesEntries.forEach((entrie) => {
const globalVariableObj = globalVariables[entrie];
rows.push({
type: globalVariableObj.type,
id: globalVariableObj.id,
default_fields: (globalVariableObj.default_fields ?? []).join(", "),
name: entrie,
});
});
setRowData(rows);
}, [globalVariables]);
const DropdownEditor = ({ options, value, onValueChange }) => { function getKeys() {
setLoadingKeys(true);
if (userData) {
getApiKey()
.then((keys: [ApiKey]) => {
keysList.current = keys["api_keys"].map((apikey: ApiKey) => ({
...apikey,
last_used_at: apikey.last_used_at ?? "Never",
}));
setUserId(keys["user_id"]);
setLoadingKeys(false);
})
.catch((error) => {
setLoadingKeys(false);
});
}
}
function resetFilter() {
getKeys();
}
function handleDeleteKey() {
Promise.all(selectedRows.map((selectedRow) => deleteApiKey(selectedRow)))
.then((res) => {
resetFilter();
setSuccessData({
title:
selectedRows.length === 1
? DEL_KEY_SUCCESS_ALERT
: DEL_KEY_SUCCESS_ALERT_PLURAL,
});
})
.catch((error) => {
setErrorData({
title:
selectedRows.length === 1
? DEL_KEY_ERROR_ALERT
: DEL_KEY_ERROR_ALERT_PLURAL,
list: [error["response"]["data"]["detail"]],
});
});
}
function lastUsedMessage() {
return ( return (
<Dropdown options={options} value={value} onSelect={onValueChange}> <div className="text-xs">
<div className="-mt-1.5 w-full"></div> <span>
</Dropdown> {LAST_USED_SPAN_1}
<br></br> {LAST_USED_SPAN_2}
</span>
</div>
); );
}; }
// Column Definitions: Defines the columns to be displayed.
const [colDefs, setColDefs] = useState<(ColDef<any> | ColGroupDef<any>)[]>([ const columnDefs = [
{ {
headerCheckboxSelection: true, headerCheckboxSelection: true,
checkboxSelection: true, checkboxSelection: true,
showDisabledCheckboxes: true, showDisabledCheckboxes: true,
headerName: "Variable Name", headerName: "Name",
field: "name", field: "name",
cellRenderer: TableAutoCellRender,
flex: 2, flex: 2,
}, //This column will be twice as wide as the others
{
field: "type",
cellRenderer: BadgeRenderer,
cellEditor: DropdownEditor,
cellEditorParams: {
options: ["Generic", "Credential"],
},
flex: 1,
editable: false,
}, },
// {
// field: "value",
// cellEditor: "agLargeTextCellEditor",
// flex: 2,
// editable: false,
// },
{ {
headerName: "Apply To Fields", headerName: "Key",
field: "default_fields", field: "api_key",
cellRenderer: TableAutoCellRender,
flex: 1,
},
{
headerName: "Created",
field: "created_at",
cellRenderer: TableAutoCellRender,
flex: 1,
},
{
headerName: "Last Used",
field: "last_used_at",
cellRenderer: TableAutoCellRender,
flex: 1,
},
{
headerName: "Total Uses",
field: "total_uses",
cellRenderer: TableAutoCellRender,
flex: 1, flex: 1,
editable: false,
resizable: false, resizable: false,
}, },
]); ];
const [selectedRows, setSelectedRows] = useState<string[]>([]);
async function removeVariables() {
const deleteGlobalVariablesPromise = selectedRows.map(async (row) => {
const id = getVariableId(row);
const deleteGlobalVariables = deleteGlobalVariable(id!);
await deleteGlobalVariables;
});
Promise.all(deleteGlobalVariablesPromise)
.then(() => {
selectedRows.forEach((row) => {
removeGlobalVariable(row);
});
})
.catch(() => {
setErrorData({
title: `Error deleting global variables.`,
});
});
}
return ( return (
<div className="flex h-full w-full flex-col justify-between gap-6"> <div className="flex h-full w-full flex-col justify-between gap-6">
<div className="flex w-full items-center justify-between gap-4 space-y-0.5"> <div className="flex w-full items-center justify-between gap-4 space-y-0.5">
<div className="flex w-full flex-col"> <div className="flex w-full flex-col">
<h2 className="flex items-center text-lg font-semibold tracking-tight"> <h2 className="flex items-center text-lg font-semibold tracking-tight">
Global Variables API Keys
<ForwardedIconComponent <ForwardedIconComponent
name="Globe" name="Key"
className="ml-2 h-5 w-5 text-primary" className="ml-2 h-5 w-5 text-primary"
/> />
</h2> </h2>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">{API_PAGE_PARAGRAPH}</p>
Manage global variables and assign them to fields.
</p>
</div> </div>
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
<Button <Button
@ -152,7 +157,7 @@ export default function ApiKeysPage() {
variant="primary" variant="primary"
className="group px-2" className="group px-2"
disabled={selectedRows.length === 0} disabled={selectedRows.length === 0}
onClick={removeVariables} onClick={handleDeleteKey}
> >
<IconComponent <IconComponent
name="Trash2" name="Trash2"
@ -161,12 +166,19 @@ export default function ApiKeysPage() {
)} )}
/> />
</Button> </Button>
<AddNewVariableButton> <SecretKeyModal
title="Create new secret key"
cancelText="Cancel"
confirmationText="Create secret key"
icon={"Key"}
data={userId}
onCloseModal={getKeys}
>
<Button data-testid="api-key-button-store" variant="primary"> <Button data-testid="api-key-button-store" variant="primary">
<IconComponent name="Plus" className="mr-2 w-4" /> <IconComponent name="Plus" className="mr-2 w-4" />
Add New Add New
</Button> </Button>
</AddNewVariableButton> </SecretKeyModal>
</div> </div>
</div> </div>
@ -177,14 +189,14 @@ export default function ApiKeysPage() {
overlayNoRowsTemplate="No data available" overlayNoRowsTemplate="No data available"
onSelectionChanged={(event: SelectionChangedEvent) => { onSelectionChanged={(event: SelectionChangedEvent) => {
setSelectedRows( setSelectedRows(
event.api.getSelectedRows().map((row) => row.name), event.api.getSelectedRows().map((row) => row.id),
); );
}} }}
rowSelection="multiple" rowSelection="multiple"
suppressRowClickSelection={true} suppressRowClickSelection={true}
pagination={true} pagination={true}
columnDefs={colDefs} columnDefs={columnDefs}
rowData={rowData} rowData={keysList.current}
/> />
</CardContent> </CardContent>
</Card> </Card>