diff --git a/.gitignore b/.gitignore index 37f0f70..88c6cac 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -summerizer.py \ No newline at end of file +summerizer.py +node_modules diff --git a/src/components/Modals/ApiKeyCreationModal.js b/src/components/Modals/ApiKeyCreationModal.js new file mode 100644 index 0000000..b901452 --- /dev/null +++ b/src/components/Modals/ApiKeyCreationModal.js @@ -0,0 +1,164 @@ +import React, { useState } from 'react'; +import { useCreateApiKey } from '../../utils/queries/apikey-queries'; + +const AVAILABLE_ROLES = ['guest', 'creator', 'admin']; + +const ApiKeyCreationModal = ({ isOpen, onClose }) => { + const [name, setName] = useState(''); + const [roles, setRoles] = useState(['guest']); + const [generatedKey, setGeneratedKey] = useState(null); + const createApiKeyMutation = useCreateApiKey(); + + const handleAddRole = () => { + const availableRoles = AVAILABLE_ROLES.filter(role => !roles.includes(role)); + if (availableRoles.length > 0) { + setRoles([...roles, availableRoles[0]]); + } + }; + + const handleRoleChange = (index, value) => { + if (roles.includes(value) && roles.findIndex(r => r === value) !== index) { + return; + } + const newRoles = [...roles]; + newRoles[index] = value; + setRoles(newRoles); + }; + + const handleRemoveRole = (index) => { + const newRoles = roles.filter((_, i) => i !== index); + setRoles(newRoles); + }; + + const handleGenerate = async () => { + if (!name.trim()) { + alert('API key name is required'); + return; + } + try { + const result = await createApiKeyMutation.mutateAsync({ + name: name.trim(), + roles: roles + }); + setGeneratedKey(result); + } catch (error) { + console.error('failed to create api key', error); + alert('failed to create api key'); + } + }; + + const handleCopy = () => { + navigator.clipboard.writeText(generatedKey) + .then(() => alert('API key copied to clipboard')) + .catch(err => console.error('failed to copy api key:', err)); + }; + + const getRemainingRoles = (currentIndex) => { + return AVAILABLE_ROLES.filter(role => + !roles.find((r, i) => r === role && i !== currentIndex) + ); + }; + + if (!isOpen) return null; + + return ( +
+
+
+
+

Create API Key

+ +
+
+ {!generatedKey ? ( +
+
+ +
+ setName(e.target.value)} + /> +
+
+
+ + {roles.map((role, index) => ( +
+
+
+ +
+
+
+ +
+
+ ))} + +
+
+ ) : ( +
+
+ Please copy your API key immediately! It will only be displayed once! +
+
+ +
+ +
+
+ +
+ )} +
+ +
+
+ ); +}; + +export default ApiKeyCreationModal; \ No newline at end of file diff --git a/src/components/Modals/ApiKeyRevokeModal.js b/src/components/Modals/ApiKeyRevokeModal.js new file mode 100644 index 0000000..32f7d89 --- /dev/null +++ b/src/components/Modals/ApiKeyRevokeModal.js @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import { useRevokeApiKey } from '../../utils/queries/apikey-queries'; + +const ApiKeyRevokeModal = ({ isOpen, onClose }) => { + const [apiKey, setApiKey] = useState(''); + const revokeApiKeyMutation = useRevokeApiKey(); + + const handleRevoke = async () => { + if (!apiKey.trim()) { + alert('Please enter an API key'); + return; + } + + try { + await revokeApiKeyMutation.mutateAsync(apiKey); + alert('API key revoked successfully'); + onClose(); + } catch (error) { + console.error('Failed to revoke API key:', error); + alert('Failed to revoke API key'); + } + }; + + if (!isOpen) return null; + + return ( +
+
+
+
+

Revoke API Key

+ +
+
+
+ +
+ setApiKey(e.target.value)} + /> +
+
+
+ +
+
+ ); +}; + +export default ApiKeyRevokeModal; \ No newline at end of file diff --git a/src/components/Navigations/MainNavigation.js b/src/components/Navigations/MainNavigation.js index b8ff456..9922cb6 100644 --- a/src/components/Navigations/MainNavigation.js +++ b/src/components/Navigations/MainNavigation.js @@ -4,11 +4,16 @@ import { AuthContext } from "../../AuthProvider"; import "bulma/css/bulma.min.css"; import {useConfig} from "../../ConfigProvider"; import "./MainNavigation.css"; +import ApiKeyCreationModal from "../Modals/ApiKeyCreationModal"; +import ApiKeyRevokeModal from "../Modals/ApiKeyRevokeModal"; const MainNavigation = () => { const { user, login, logout } = useContext(AuthContext); const config = useConfig(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false); + const [isRevokeModalOpen, setIsRevokeModalOpen] = useState(false); + if (config===undefined) { return
Loading ...
; } @@ -88,102 +93,124 @@ const MainNavigation = () => { }; return ( - + setIsApiKeyModalOpen(false)} + /> + setIsRevokeModalOpen(false)} + /> + ); }; diff --git a/src/utils/queries/apikey-queries.js b/src/utils/queries/apikey-queries.js new file mode 100644 index 0000000..34e0041 --- /dev/null +++ b/src/utils/queries/apikey-queries.js @@ -0,0 +1,32 @@ +import { useConfig } from "../../ConfigProvider"; +import { useMutation } from "@tanstack/react-query"; +import { fetch_ } from "../request-utils"; + +export const useCreateApiKey = () => { + const config = useConfig(); + return useMutation({ + mutationFn: async ({ name, roles }) => { + const response = await fetch_(`${config.BACKEND_HOST}/api/apikey/`, { + method: "POST", + body: JSON.stringify({ name, roles }), + }); + console.log("response", response); + return response; + }, + cacheTime: 0, + }); +}; + +export const useRevokeApiKey = () => { + const config = useConfig(); + return useMutation({ + mutationFn: async (apiKey) => { + const response = await fetch_(`${config.BACKEND_HOST}/api/apikey/revoke`, { + method: "POST", + body: JSON.stringify({ apiKey }), + }); + return response; + }, + cacheTime: 0, + }); +}; \ No newline at end of file