From 3cd8aff96ee71ba3675913c50e4421b7aebd357d Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 20:32:53 +0100 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=90=9B=20fix(auth.py):=20change=20cre?= =?UTF-8?q?ate=5Fuser=5Flongterm=5Ftoken=20to=20use=20create=5Fsuper=5Fuse?= =?UTF-8?q?r=20to=20ensure=20super=20user=20exists=20before=20creating=20t?= =?UTF-8?q?oken=20=E2=9C=A8=20feat(auth.py):=20add=20create=5Fsuper=5Fuser?= =?UTF-8?q?=20function=20to=20create=20super=20user=20if=20it=20doesn't=20?= =?UTF-8?q?exist=20=F0=9F=90=9B=20fix(login.py):=20remove=20hardcoded=20us?= =?UTF-8?q?er=5Fid=20and=20use=20create=5Fuser=5Flongterm=5Ftoken=20withou?= =?UTF-8?q?t=20arguments=20=F0=9F=90=9B=20fix(users.py):=20remove=20redund?= =?UTF-8?q?ant=20password=20hashing=20in=20add=5Fsuper=5Fuser=5Ffor=5Ftest?= =?UTF-8?q?ing=5Fpurposes=5Fdelete=5Fme=5Fbefore=5Fmerge=5Finto=5Fdev=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(base.py):=20change=20AUTO=5FLOGIN=20default?= =?UTF-8?q?=20value=20to=20False=20and=20add=20FIRST=5FSUPERUSER=20and=20F?= =?UTF-8?q?IRST=5FSUPERUSER=5FPASSWORD=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/auth/auth.py | 34 +++++++++++++++---- src/backend/langflow/routers/login.py | 4 +-- src/backend/langflow/routers/users.py | 3 +- .../langflow/services/settings/base.py | 5 ++- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py index b9e8dba3a..28d242342 100644 --- a/src/backend/langflow/auth/auth.py +++ b/src/backend/langflow/auth/auth.py @@ -83,18 +83,40 @@ def create_token(data: dict, expires_delta: timedelta): ) -def create_user_longterm_token( - user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False -) -> dict: +def create_super_user(db: Session = Depends(get_session)) -> User: + settings_manager = get_settings_manager() + + super_user = get_user_by_username(db, settings_manager.settings.FIRST_SUPERUSER) + + if not super_user: + super_user = User( + username=settings_manager.settings.FIRST_SUPERUSER, + password=get_password_hash( + settings_manager.settings.FIRST_SUPERUSER_PASSWORD + ), + is_superuser=True, + is_active=True, + last_login_at=None, + ) + + db.add(super_user) + db.commit() + db.refresh(super_user) + + return super_user + + +def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: + super_user = create_super_user(db) + access_token_expires_longterm = timedelta(days=365) access_token = create_token( - data={"sub": str(user_id)}, + data={"sub": str(super_user.id)}, expires_delta=access_token_expires_longterm, ) # Update: last_login_at - if update_last_login: - update_user_last_login_at(user_id, db) + update_user_last_login_at(super_user.id, db) return { "access_token": access_token, diff --git a/src/backend/langflow/routers/login.py b/src/backend/langflow/routers/login.py index 7d114473d..de255a0d5 100644 --- a/src/backend/langflow/routers/login.py +++ b/src/backend/langflow/routers/login.py @@ -1,4 +1,3 @@ -from uuid import UUID from sqlalchemy.orm import Session from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm @@ -38,8 +37,7 @@ async def auto_login(db: Session = Depends(get_session)): settings_manager = get_settings_manager() if settings_manager.settings.AUTO_LOGIN: - user_id = UUID("3fa85f64-5717-4562-b3fc-2c963f66afa6") - return create_user_longterm_token(user_id, db) + return create_user_longterm_token(db) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/src/backend/langflow/routers/users.py b/src/backend/langflow/routers/users.py index da738a5cd..04972c976 100644 --- a/src/backend/langflow/routers/users.py +++ b/src/backend/langflow/routers/users.py @@ -115,14 +115,13 @@ def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev( """ new_user = User( username="superuser", - password="12345", + password=get_password_hash("12345"), is_active=True, is_superuser=True, last_login_at=None, ) try: - new_user.password = get_password_hash(new_user.password) db.add(new_user) db.commit() db.refresh(new_user) diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index d8b3f3ad6..d99f0f8b5 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -41,9 +41,12 @@ class Settings(BaseSettings): ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 REFRESH_TOKEN_EXPIRE_MINUTES: int = 70 + # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. - AUTO_LOGIN: bool = True + AUTO_LOGIN: bool = False + FIRST_SUPERUSER: str = "superuser" + FIRST_SUPERUSER_PASSWORD: str = "12345" @validator("DATABASE_URL", pre=True) def set_database_url(cls, value): From effbddbcb094215d3d84ebdbacc874c2c216ecb7 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 20:56:10 +0100 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=90=9B=20fix(base.py):=20set=20AUTO?= =?UTF-8?q?=5FLOGIN=20to=20True=20to=20enable=20automatic=20login=20as=20a?= =?UTF-8?q?=20super=20user=20=F0=9F=94=A7=20chore(base.py):=20improve=20re?= =?UTF-8?q?adability=20by=20formatting=20logger.debug=20statements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/services/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index d99f0f8b5..f1b012f4d 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -44,7 +44,7 @@ class Settings(BaseSettings): # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. - AUTO_LOGIN: bool = False + AUTO_LOGIN: bool = True FIRST_SUPERUSER: str = "superuser" FIRST_SUPERUSER_PASSWORD: str = "12345" From 91fcf33506ca227a80d1a305e7a9699ce37f7bc5 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 23:47:50 +0100 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=90=9B=20fix(auth.py):=20import=20get?= =?UTF-8?q?=5Fsession=20from=20correct=20module=20to=20fix=20import=20erro?= =?UTF-8?q?r=20=E2=9C=A8=20feat(auth.py):=20add=20support=20for=20creating?= =?UTF-8?q?=20user=20API=20key=20and=20getting=20user=20ID=20from=20token?= =?UTF-8?q?=20=F0=9F=90=9B=20fix(base.py):=20fix=20typo=20in=20API=5FKEY?= =?UTF-8?q?=5FSECRET=5FKEY=20variable=20name=20=F0=9F=90=9B=20fix(base.py)?= =?UTF-8?q?:=20fix=20typo=20in=20FIRST=5FSUPERUSER=20and=20FIRST=5FSUPERUS?= =?UTF-8?q?ER=5FPASSWORD=20variable=20names=20=F0=9F=90=9B=20fix(base.py):?= =?UTF-8?q?=20fix=20indentation=20in=20load=5Fsettings=5Ffrom=5Fyaml=20fun?= =?UTF-8?q?ction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/auth/auth.py | 20 +++++++++++++++++-- .../langflow/services/settings/base.py | 10 ++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py index 28d242342..9d4f12862 100644 --- a/src/backend/langflow/auth/auth.py +++ b/src/backend/langflow/auth/auth.py @@ -7,9 +7,8 @@ from fastapi.security import OAuth2PasswordBearer from fastapi import Depends, HTTPException, status from datetime import datetime, timedelta, timezone -from langflow.services.utils import get_settings_manager +from langflow.services.utils import get_settings_manager, get_session -from langflow.services.utils import get_session from langflow.database.models.user import ( User, get_user_by_id, @@ -125,6 +124,23 @@ def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: } +def create_user_api_key(user_id: UUID) -> dict: + access_token = create_token( + data={"sub": str(user_id), "role": "api_key"}, + expires_delta=timedelta(days=365 * 2), + ) + + return {"api_key": access_token} + + +def get_user_id_from_token(token: str) -> UUID: + try: + user_id = jwt.get_unverified_claims(token)["sub"] + return UUID(user_id) + except (KeyError, JWTError, ValueError): + return UUID(int=0) + + def create_user_tokens( user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False ) -> dict: diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index f1b012f4d..ec976bade 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -42,11 +42,17 @@ class Settings(BaseSettings): ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 REFRESH_TOKEN_EXPIRE_MINUTES: int = 70 + # API Key to execute /process endpoint + API_KEY_SECRET_KEY: Optional[ + str + ] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a" + API_KEY_ALGORITHM: str = "HS256" + # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. AUTO_LOGIN: bool = True - FIRST_SUPERUSER: str = "superuser" - FIRST_SUPERUSER_PASSWORD: str = "12345" + FIRST_SUPERUSER: str = "langflow" + FIRST_SUPERUSER_PASSWORD: str = "langflow" @validator("DATABASE_URL", pre=True) def set_database_url(cls, value): From 4eeb9449cb607c26688c6c2a63f5b53cb648ce01 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Wed, 16 Aug 2023 20:20:18 +0100 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=94=A7=20fix(main.py):=20reformat=20i?= =?UTF-8?q?mport=20statements=20to=20improve=20readability=20and=20maintai?= =?UTF-8?q?nability=20=E2=9C=A8=20feat(main.py):=20add=20support=20for=20A?= =?UTF-8?q?PI=20key=20routes=20to=20enable=20API=20key=20management=20func?= =?UTF-8?q?tionality=20=F0=9F=93=9D=20docs(api=5Fkey.py):=20add=20API=20ke?= =?UTF-8?q?y=20routes=20for=20retrieving,=20creating,=20and=20deleting=20A?= =?UTF-8?q?PI=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/main.py | 3 +- src/backend/langflow/routers/api_key.py | 49 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/backend/langflow/routers/api_key.py diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index b63caff24..7045ec99d 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -6,7 +6,7 @@ from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from langflow.api import router -from langflow.routers import login, users, health +from langflow.routers import api_key, login, users, health from langflow.interface.utils import setup_llm_caching from langflow.services.database.utils import initialize_database @@ -32,6 +32,7 @@ def create_app(): ) app.include_router(login.router) + app.include_router(api_key.router) app.include_router(users.router) app.include_router(health.router) diff --git a/src/backend/langflow/routers/api_key.py b/src/backend/langflow/routers/api_key.py new file mode 100644 index 000000000..7cc712962 --- /dev/null +++ b/src/backend/langflow/routers/api_key.py @@ -0,0 +1,49 @@ + + +from fastapi import APIRouter + + + +router = APIRouter(tags=["APIKey"]) + + +@router.get("/api_key/{user_id}") +def get_api_key(user_id: str): + return { + "total_count": 3, + "user_id": user_id, + "api_keys": [ + { + "id": "4425707e-cce4-4d1b-a54e-bd2632064657", + "name": "my api_key name - 01", + "created_at": "2023-08-15T19:28:40.019613", + "last_used_at": "2023-08-16T18:38:20.875210", + }, + { + "id": "6fb7282b-9f2e-4efe-9bda-0c3d8f899473", + "name": "my api_key name - 02", + "created_at": "2023-08-15T19:41:30.077942", + "last_used_at": "2023-08-15T19:45:32.067899", + }, + { + "id": "c55f3b32-4920-42b6-a5cd-698b4251806e", + "name": "my api_key name - 03", + "created_at": "2023-08-15T20:29:40.577808", + "last_used_at": "2023-08-15T20:29:40.577816", + }, + ], + } + + +@router.post("/api_key/{user_id}") +def create_api_key(user_id: str): + return { + "user_id": user_id, + "name": "my api-key 01", + "api_key": "lf-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YTBmODM1ZS0yMTQxLTQ2YWItYmQ4NS0yMWEzMjQ1MTE2ZDAiLCJleHAiOjE2OTIyMTUwMTN9.c_s0ZPRtjSI9yUrhi8ACIwyXf0feRLYfaeIZEbRVKQg", + } + + +@router.delete("/api_key/{api_key_id}") +def delete_api_key(api_key_id: str): + return {"detail": "API Key deleted"} From ba48bbe770e9642765a9471b43bf6dd44df8a2f5 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Wed, 16 Aug 2023 16:38:13 -0300 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=90=9B=20fix(App.tsx):=20remove=20unn?= =?UTF-8?q?ecessary=20code=20block=20in=20useEffect=20=F0=9F=94=A7=20chore?= =?UTF-8?q?(constants.ts):=20add=20CONTROL=5FNEW=5FAPI=5FKEY=20constant=20?= =?UTF-8?q?for=20consistency=20=E2=9C=A8=20feat(SecretKeyModal):=20add=20S?= =?UTF-8?q?ecretKeyModal=20component=20to=20handle=20secret=20key=20genera?= =?UTF-8?q?tion=20and=20copying=20=F0=9F=94=A7=20chore(UserManagementModal?= =?UTF-8?q?):=20rearrange=20buttons=20in=20UserManagementModal=20for=20bet?= =?UTF-8?q?ter=20user=20experience?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚀 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 --- src/frontend/src/App.tsx | 3 - src/frontend/src/constants/constants.ts | 4 + .../src/modals/SecretKeyModal/index.tsx | 191 ++++++++++ .../src/modals/UserManagementModal/index.tsx | 11 +- src/frontend/src/pages/ApiKeysPage/index.tsx | 349 ++++++++++++++++++ src/frontend/src/routes.tsx | 9 + src/frontend/src/types/components/index.ts | 15 + src/frontend/src/utils/styleUtils.ts | 4 +- 8 files changed, 578 insertions(+), 8 deletions(-) create mode 100644 src/frontend/src/modals/SecretKeyModal/index.tsx create mode 100644 src/frontend/src/pages/ApiKeysPage/index.tsx diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 99901e3a0..25b21020c 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -150,9 +150,6 @@ export default function App() { }) .catch((error) => {}); } - else{ - navigate("/login"); - } }); }, 500); }, []); diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index a8881d7a5..b43954067 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -528,6 +528,10 @@ export const CONTROL_NEW_USER = { is_superuser: false, }; +export const CONTROL_NEW_API_KEY = { + apikeyname: "", +}; + export const tabsCode = []; export function tabsArray(codes: string[], method: number) { diff --git a/src/frontend/src/modals/SecretKeyModal/index.tsx b/src/frontend/src/modals/SecretKeyModal/index.tsx new file mode 100644 index 000000000..a5a3143f6 --- /dev/null +++ b/src/frontend/src/modals/SecretKeyModal/index.tsx @@ -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(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 ( + + {children} + + {title} + + + {renderKey === true && ( + <> + + Please save this secret key somewhere safe and accessible. For + security reasons,{" "} + you won't be able to view it again through your + account. If you lose this secret key, you'll need to generate a + new one. + +
+
+ { + setApiKeyValue(event.target.value); + }} + readOnly={true} + value={apiKeyValue} + /> +
+ +
+ +
+
+ + )} + + { + setRenderKey(true); + event.preventDefault(); + }} + > + {renderKey === false && ( +
+ +
+ + Name (optional){" "} + +
+ + { + handleInput({ target: { name: "apikeyname", value } }); + setApiKeyName(value); + }} + value={apiKeyName} + className="primary-input" + placeholder="My key name" + /> + +
+
+ )} + {renderKey === false && ( +
+ + + + + +
+ )} + + {renderKey === true && ( +
+ +
+ )} +
+
+
+ ); +} diff --git a/src/frontend/src/modals/UserManagementModal/index.tsx b/src/frontend/src/modals/UserManagementModal/index.tsx index d370ef841..993ffb944 100644 --- a/src/frontend/src/modals/UserManagementModal/index.tsx +++ b/src/frontend/src/modals/UserManagementModal/index.tsx @@ -273,17 +273,20 @@ export default function UserManagementModal({
- - - - + + + + +
diff --git a/src/frontend/src/pages/ApiKeysPage/index.tsx b/src/frontend/src/pages/ApiKeysPage/index.tsx new file mode 100644 index 000000000..a08e155f4 --- /dev/null +++ b/src/frontend/src/pages/ApiKeysPage/index.tsx @@ -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 ( +
+ + The last time this key was used.

Accurate to within the hour + from the most recent usage. +
+
+ ); + } + + return ( + <> + {userData && ( +
+
+
+
+
+
+

+ API keys +

+

+ Your secret API keys are listed below. Please note that we + do not display your secret API keys again after you + generate them.

+ Do not share your API key with others, or expose it in the + browser or other client-side code. +

+
+
+
+ + {userList.current.length === 0 && !loadingUsers && ( + <> +
+

There's no users registered :)

+
+ + )} + <> + {loadingUsers && ( +
+ Loading... +
+ )} +
+ + + + Name + Key + Created + + Last Used + +
+ +
+
+
+ +
+
+ {!loadingUsers && ( + + {filterUserList.map((user, index) => ( + + + + + {user.id} + + + + + + + {user.username} + + + + + { + new Date(user.create_at) + .toISOString() + .split("T")[0] + } + + + { + new Date(user.updated_at) + .toISOString() + .split("T")[0] + } + + +
+ { + handleDeleteUser(user); + }} + > + + + + +
+
+
+ ))} +
+ )} +
+
+ +
+
+ { + handleNewUser(user); + }} + > + + +
+
+ +
+
+
+
+ )} + + ); +} diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 81421d057..88d485155 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -11,6 +11,7 @@ import HomePage from "./pages/MainPage"; import DeleteAccountPage from "./pages/deleteAccountPage"; import LoginPage from "./pages/loginPage"; import SignUp from "./pages/signUpPage"; +import ApiKeysPage from "./pages/ApiKeysPage"; const Router = () => { return ( @@ -93,6 +94,14 @@ const Router = () => { } > + + + + } + > ); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index ddac47d9f..053eb50df 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -233,3 +233,18 @@ export type UserInputType = { is_active?: 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; +}; \ No newline at end of file diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 9f4ebe21d..0f82635f6 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -72,6 +72,7 @@ import { X, XCircle, Zap, + Key } from "lucide-react"; import { FaApple, FaGithub } from "react-icons/fa"; import { AirbyteIcon } from "../icons/Airbyte"; @@ -294,5 +295,6 @@ export const nodeIconsLucide = { FaApple, EyeOff, Eye, - UserCog2 + UserCog2, + Key };