diff --git a/src/backend/langflow/api/v1/users.py b/src/backend/langflow/api/v1/users.py index 7365e7cc1..5094409cb 100644 --- a/src/backend/langflow/api/v1/users.py +++ b/src/backend/langflow/api/v1/users.py @@ -43,7 +43,9 @@ def add_user( db.refresh(new_user) except IntegrityError as e: db.rollback() - raise HTTPException(status_code=400, detail="This username is unavailable.") from e + raise HTTPException( + status_code=400, detail="This username is unavailable." + ) from e return new_user diff --git a/src/frontend/src/components/DropdownButtonComponent/index.tsx b/src/frontend/src/components/DropdownButtonComponent/index.tsx new file mode 100644 index 000000000..1ddddb3c1 --- /dev/null +++ b/src/frontend/src/components/DropdownButtonComponent/index.tsx @@ -0,0 +1,67 @@ +import { useState } from "react"; +import { dropdownButtonPropsType } from "../../types/components"; +import IconComponent from "../genericIconComponent"; +import { Button } from "../ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; + +export default function DropdownButton({ + firstButtonName, + onFirstBtnClick, + options, +}: dropdownButtonPropsType): JSX.Element { + const [showOptions, setShowOptions] = useState(false); + + return ( +
+ + + + + { + event.stopPropagation(); + event.preventDefault(); + setShowOptions(!showOptions); + }} + > + {options.map(({ name, onBtnClick }, index) => ( + + {name} + + ))} + + +
+ ); +} diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 97ad50329..339ad67b5 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -48,7 +48,7 @@ const TabsContextInitialValue: TabsContextType = { downloadFlow: (flow: FlowType) => {}, downloadFlows: () => {}, uploadFlows: () => {}, - uploadFlow: () => {}, + uploadFlow: async () => "", isBuilt: false, setIsBuilt: (state: boolean) => {}, hardReset: () => {}, @@ -298,39 +298,38 @@ export function TabsProvider({ children }: { children: ReactNode }) { * If the file type is application/json, the file is read and parsed into a JSON object. * The resulting JSON object is passed to the addFlow function. */ - function uploadFlow(newProject?: boolean, file?: File) { + async function uploadFlow( + newProject?: boolean, + file?: File + ): Promise { + let id; if (file) { - file.text().then((text) => { - // parse the text into a JSON object - let flow: FlowType = JSON.parse(text); + let text = await file.text(); + // parse the text into a JSON object + let flow: FlowType = JSON.parse(text); - addFlow(flow, newProject); - }); + id = await addFlow(flow, newProject); } else { // create a file input const input = document.createElement("input"); input.type = "file"; input.accept = ".json"; // add a change event listener to the file input - input.onchange = (e: Event) => { - // check if the file type is application/json - if ( - (e.target as HTMLInputElement).files![0].type === "application/json" - ) { - // get the file from the file input - const currentfile = (e.target as HTMLInputElement).files![0]; - // read the file as text - currentfile.text().then((text) => { - // parse the text into a JSON object + id = await new Promise(resolve => { + input.onchange = async (e: Event) => { + if ((e.target as HTMLInputElement).files![0].type === "application/json") { + const currentfile = (e.target as HTMLInputElement).files![0]; + let text = await currentfile.text(); let flow: FlowType = JSON.parse(text); - - addFlow(flow, newProject); - }); - } - }; - // trigger the file input click event to open the file dialog - input.click(); + const flowId = await addFlow(flow, newProject); + resolve(flowId); + } + }; + // trigger the file input click event to open the file dialog + input.click(); + }); } + return id; } function uploadFlows() { diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index fc963fee8..08e83f700 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -4,6 +4,7 @@ import { useContext, useEffect, useRef, useState } from "react"; import PaginatorComponent from "../../components/PaginatorComponent"; import ShadTooltip from "../../components/ShadTooltipComponent"; import IconComponent from "../../components/genericIconComponent"; +import Header from "../../components/headerComponent"; import { Button } from "../../components/ui/button"; import { Checkbox } from "../../components/ui/checkbox"; import { Input } from "../../components/ui/input"; @@ -25,9 +26,8 @@ import { } from "../../controllers/API"; import ConfirmationModal from "../../modals/ConfirmationModal"; import UserManagementModal from "../../modals/UserManagementModal"; -import { UserInputType } from "../../types/components"; -import Header from "../../components/headerComponent"; import { Users } from "../../types/api"; +import { UserInputType } from "../../types/components"; export default function AdminPage() { const [inputValue, setInputValue] = useState(""); @@ -89,7 +89,7 @@ export default function AdminPage() { if (input === "") { setFilterUserList(userList.current); } else { - const filteredList = userList.current.filter((user:Users) => + const filteredList = userList.current.filter((user: Users) => user.username.toLowerCase().includes(input.toLowerCase()) ); setFilterUserList(filteredList); @@ -183,249 +183,254 @@ export default function AdminPage() { return ( <> -
-
- {userData && ( -
-
-
-
-
-
-

- Welcome back! -

-

- Navigate through this section to efficiently oversee all - application users. From here, you can seamlessly manage - user accounts. -

+
+
+ {userData && ( +
+
+
+
+
+
+

+ Welcome back! +

+

+ Navigate through this section to efficiently oversee all + application users. From here, you can seamlessly manage + user accounts. +

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

There's no users registered :)

+
+ + )} <>
-

There's no users registered :)

-
- - )} - <> -
-
- handleFilterUsers(e.target.value)} - /> - {inputValue.length > 0 && ( - + )} +
+
+ { + handleNewUser(user); }} - variant="ghost" - className="h-8 px-2 lg:px-3" > - Reset - - - )} + + +
-
- { - handleNewUser(user); - }} - > - - -
-
- {loadingUsers && ( -
- Loading... -
- )} -
- - - - Id - Username - Active - Superuser - Created At - Updated At - - - - {!loadingUsers && ( - - {filterUserList.map((user:UserInputType, index) => ( - - - - - {user.id} - - - - - - - {user.username} - - - - - { - handleDisableUser( - user.is_active, - user.id, - user - ); - }} - > - - - - - { - handleSuperUserEdit( - user.is_superuser, - user.id, - user - ); - }} - > - - - - - { - new Date(user.create_at!) - .toISOString() - .split("T")[0] - } - - - { - new Date(user.updated_at!) - .toISOString() - .split("T")[0] - } - - -
- { - handleEditUser(user.id, editUser); - }} - > - - + {loadingUsers && ( +
+ Loading... +
+ )} +
+
+ + + Id + Username + Active + Superuser + Created At + Updated At + + + + {!loadingUsers && ( + + {filterUserList.map( + (user: UserInputType, index) => ( + + + + + {user.id} + - - - { - handleDeleteUser(user); - }} - > - - + + + + + {user.username} + - - - - - ))} - - )} -
-
+ + + { + handleDisableUser( + user.is_active, + user.id, + user + ); + }} + > + + + + + { + handleSuperUserEdit( + user.is_superuser, + user.id, + user + ); + }} + > + + + + + { + new Date(user.create_at!) + .toISOString() + .split("T")[0] + } + + + { + new Date(user.updated_at!) + .toISOString() + .split("T")[0] + } + + +
+ { + handleEditUser(user.id, editUser); + }} + > + + + + - { - handleChangePagination(pageSize, pageIndex); - }} - > - + { + handleDeleteUser(user); + }} + > + + + + +
+
+ + ) + )} + + )} + +
+ + { + handleChangePagination(pageSize, pageIndex); + }} + > + +
-
- )} + )}
); diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 058be6e5c..620170fbb 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -421,7 +421,7 @@ export default function Page({ zoomOnScroll={!view} zoomOnPinch={!view} panOnDrag={!view} - proOptions={{hideAttribution: true}} + proOptions={{ hideAttribution: true }} > {!view && ( diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index 442a43e2f..364519ea8 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -7,6 +7,7 @@ import { SkeletonCardComponent } from "../../components/skeletonCardComponent"; import { Button } from "../../components/ui/button"; import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { TabsContext } from "../../contexts/tabsContext"; +import DropdownButton from "../../components/DropdownButtonComponent"; export default function HomePage(): JSX.Element { const { flows, @@ -14,9 +15,12 @@ export default function HomePage(): JSX.Element { downloadFlows, uploadFlows, addFlow, - removeFlow, + removeFlow, uploadFlow, isLoading, } = useContext(TabsContext); + const dropdownOptions = [{name: "Import from JSON", onBtnClick: () => uploadFlow(true).then((id) => { + navigate("/flow/" + id); + })}] // Set a null id useEffect(() => { @@ -57,21 +61,19 @@ export default function HomePage(): JSX.Element { Upload Collection - + options={dropdownOptions} + />
- Manage your personal projects. Download or upload your collection. + Manage your personal projects. Download or upload your collection.
{isLoading && flows.length == 0 ? ( diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index fdd586b55..181486c99 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -19,7 +19,8 @@ export default function LoginPage(): JSX.Element { useState(CONTROL_LOGIN_STATE); const { password, username } = inputState; - const { login, getAuthentication, setUserData, setIsAdmin } = useContext(AuthContext); + const { login, getAuthentication, setUserData, setIsAdmin } = + useContext(AuthContext); const navigate = useNavigate(); const { setErrorData } = useContext(alertContext); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index f7b974a93..b61ddf07d 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -268,7 +268,7 @@ export type UserInputType = { is_superuser?: boolean; id?: string; create_at?: string; - updated_at?:string; + updated_at?: string; }; export type ApiKeyType = { @@ -544,3 +544,9 @@ export type fetchErrorComponentType = { message: string; description: string; }; + +export type dropdownButtonPropsType = { + firstButtonName: string; + onFirstBtnClick: () => void; + options: Array<{ name: string; onBtnClick: () => void; }>; +}; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 036f82717..4c5b99dd7 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -24,7 +24,7 @@ export type TabsContextType = { uploadFlows: () => void; isBuilt: boolean; setIsBuilt: (state: boolean) => void; - uploadFlow: (newFlow?: boolean, file?: File) => void; + uploadFlow: (newFlow?: boolean, file?: File) => Promise; hardReset: () => void; getNodeId: (nodeType: string) => string; tabsState: TabsState; diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 31a7adf7e..e74bb0e34 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -5,6 +5,7 @@ import { ChevronDown, ChevronLeft, ChevronRight, + ChevronUp, ChevronsLeft, ChevronsRight, ChevronsUpDown, @@ -300,4 +301,5 @@ export const nodeIconsLucide: iconsType = { UserCog2, Key, Unplug, + ChevronUp, };