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 SIGNIN_ERROR_ALERT = "Error signing in";
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 WRONG_FILE_ERROR_ALERT = "Invalid file type";
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_ADD_SUCCESS_ALERT = "Success! New user added!";
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 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 API_PAGE_PARAGRAPH_1 =
"Your secret API keys are listed below. Please note that we do not display your secret API keys again after you generate them.";
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_PARAGRAPH =
"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_USER_KEYS =
"This user does not have any keys assigned at the moment.";

View file

@ -22,8 +22,7 @@ import {
DEL_KEY_SUCCESS_ALERT,
} from "../../constants/alerts_constants";
import {
API_PAGE_PARAGRAPH_1,
API_PAGE_PARAGRAPH_2,
API_PAGE_PARAGRAPH,
API_PAGE_USER_KEYS,
LAST_USED_SPAN_1,
LAST_USED_SPAN_2,
@ -128,9 +127,7 @@ export default function ApiKeysPage() {
API keys
</h2>
<p className="text-muted-foreground">
{API_PAGE_PARAGRAPH_1}
<br />
{API_PAGE_PARAGRAPH_2}
{API_PAGE_PARAGRAPH}
</p>
</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 { 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 Dropdown from "../../../../components/dropdownComponent";
import ForwardedIconComponent from "../../../../components/genericIconComponent";
import TableComponent from "../../../../components/tableComponent";
import { Badge } from "../../../../components/ui/badge";
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 { useGlobalVariablesStore } from "../../../../stores/globalVariablesStore/globalVariables";
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() {
const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries,
);
const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable,
);
const globalVariables = useGlobalVariablesStore(
(state) => state.globalVariables,
);
const [loadingKeys, setLoadingKeys] = useState(true);
const [selectedRows, setSelectedRows] = useState<string[]>([]);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
const BadgeRenderer = (props) => {
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;
}[]
>([]);
const { userData } = useContext(AuthContext);
const [userId, setUserId] = useState("");
const keysList = useRef([]);
useEffect(() => {
const rows: Array<{
type: string | undefined;
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]);
getKeys();
}, [userData]);
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 (
<Dropdown options={options} value={value} onSelect={onValueChange}>
<div className="-mt-1.5 w-full"></div>
</Dropdown>
<div className="text-xs">
<span>
{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,
checkboxSelection: true,
showDisabledCheckboxes: true,
headerName: "Variable Name",
headerName: "Name",
field: "name",
cellRenderer: TableAutoCellRender,
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",
field: "default_fields",
headerName: "Key",
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,
editable: 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 (
<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 flex-col">
<h2 className="flex items-center text-lg font-semibold tracking-tight">
Global Variables
API Keys
<ForwardedIconComponent
name="Globe"
name="Key"
className="ml-2 h-5 w-5 text-primary"
/>
</h2>
<p className="text-sm text-muted-foreground">
Manage global variables and assign them to fields.
</p>
<p className="text-sm text-muted-foreground">{API_PAGE_PARAGRAPH}</p>
</div>
<div className="flex flex-shrink-0 items-center gap-2">
<Button
@ -152,7 +157,7 @@ export default function ApiKeysPage() {
variant="primary"
className="group px-2"
disabled={selectedRows.length === 0}
onClick={removeVariables}
onClick={handleDeleteKey}
>
<IconComponent
name="Trash2"
@ -161,12 +166,19 @@ export default function ApiKeysPage() {
)}
/>
</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">
<IconComponent name="Plus" className="mr-2 w-4" />
Add New
</Button>
</AddNewVariableButton>
</SecretKeyModal>
</div>
</div>
@ -177,14 +189,14 @@ export default function ApiKeysPage() {
overlayNoRowsTemplate="No data available"
onSelectionChanged={(event: SelectionChangedEvent) => {
setSelectedRows(
event.api.getSelectedRows().map((row) => row.name),
event.api.getSelectedRows().map((row) => row.id),
);
}}
rowSelection="multiple"
suppressRowClickSelection={true}
pagination={true}
columnDefs={colDefs}
rowData={rowData}
columnDefs={columnDefs}
rowData={keysList.current}
/>
</CardContent>
</Card>