Merge branch 'dev' into newHandleId
This commit is contained in:
commit
daf9214513
154 changed files with 5962 additions and 2477 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import _ from "lodash";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import "reactflow/dist/style.css";
|
||||
import "./App.css";
|
||||
|
||||
|
|
@ -9,10 +9,16 @@ import ErrorAlert from "./alerts/error";
|
|||
import NoticeAlert from "./alerts/notice";
|
||||
import SuccessAlert from "./alerts/success";
|
||||
import CrashErrorComponent from "./components/CrashErrorComponent";
|
||||
import FetchErrorComponent from "./components/fetchErrorComponent";
|
||||
import LoadingComponent from "./components/loadingComponent";
|
||||
import {
|
||||
FETCH_ERROR_DESCRIPION,
|
||||
FETCH_ERROR_MESSAGE,
|
||||
} from "./constants/constants";
|
||||
import { alertContext } from "./contexts/alertContext";
|
||||
import { locationContext } from "./contexts/locationContext";
|
||||
import { TabsContext } from "./contexts/tabsContext";
|
||||
import { typesContext } from "./contexts/typesContext";
|
||||
import Router from "./routes";
|
||||
|
||||
export default function App() {
|
||||
|
|
@ -36,8 +42,12 @@ export default function App() {
|
|||
successData,
|
||||
successOpen,
|
||||
setSuccessOpen,
|
||||
setErrorData,
|
||||
loading,
|
||||
setLoading,
|
||||
} = useContext(alertContext);
|
||||
const navigate = useNavigate();
|
||||
const { fetchError } = useContext(typesContext);
|
||||
|
||||
// Initialize state variable for the list of alerts
|
||||
const [alertsList, setAlertsList] = useState<
|
||||
|
|
@ -137,7 +147,14 @@ export default function App() {
|
|||
>
|
||||
{loading ? (
|
||||
<div className="loading-page-panel">
|
||||
<LoadingComponent remSize={50} />
|
||||
{fetchError ? (
|
||||
<FetchErrorComponent
|
||||
description={FETCH_ERROR_DESCRIPION}
|
||||
message={FETCH_ERROR_MESSAGE}
|
||||
></FetchErrorComponent>
|
||||
) : (
|
||||
<LoadingComponent remSize={50} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -32,13 +32,6 @@ export default function AccordionComponent({
|
|||
value === "" ? setValue(keyValue!) : setValue("");
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === "Backspace") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Accordion
|
||||
|
|
@ -46,7 +39,6 @@ export default function AccordionComponent({
|
|||
className="w-full"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<AccordionItem value={keyValue!} className="border-b">
|
||||
<AccordionTrigger
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -12,17 +12,21 @@ import { Button } from "../ui/button";
|
|||
|
||||
export default function PaginatorComponent({
|
||||
pageSize = 10,
|
||||
pageIndex = 1,
|
||||
rowsCount = [10, 20, 30],
|
||||
pageIndex = 0,
|
||||
rowsCount = [10, 20, 50, 100],
|
||||
totalRowsCount = 0,
|
||||
paginate,
|
||||
}: PaginatorComponentType) {
|
||||
const [size, setPageSize] = useState(pageSize);
|
||||
const [index, setPageIndex] = useState(pageIndex);
|
||||
|
||||
const [maxIndex, setMaxPageIndex] = useState(
|
||||
Math.ceil(totalRowsCount / pageSize)
|
||||
);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
setMaxPageIndex(Math.ceil(totalRowsCount / size));
|
||||
}, [totalRowsCount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -35,7 +39,7 @@ export default function PaginatorComponent({
|
|||
onValueChange={(pageSize: string) => {
|
||||
setPageSize(Number(pageSize));
|
||||
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
|
||||
paginate(Number(pageSize), index);
|
||||
paginate(Number(pageSize), 0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[100px]">
|
||||
|
|
@ -51,30 +55,30 @@ export default function PaginatorComponent({
|
|||
</Select>
|
||||
</div>
|
||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {index} of {maxIndex}
|
||||
Page {currentPage} of {maxIndex}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
disabled={index <= 0}
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => {
|
||||
setPageIndex(1);
|
||||
paginate(size, 1);
|
||||
setPageIndex(0);
|
||||
setCurrentPage(1);
|
||||
paginate(size, 0);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={index <= 0}
|
||||
onClick={() => {
|
||||
if (index <= 1) {
|
||||
setPageIndex(1);
|
||||
paginate(size, 1);
|
||||
} else {
|
||||
{
|
||||
setPageIndex(index - 1);
|
||||
paginate(size, index - 1);
|
||||
}
|
||||
if (index > 0) {
|
||||
const pgIndex = size - index;
|
||||
setCurrentPage(currentPage - 1);
|
||||
setPageIndex(pgIndex);
|
||||
paginate(size, pgIndex);
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
|
|
@ -84,14 +88,12 @@ export default function PaginatorComponent({
|
|||
<IconComponent name="ChevronLeft" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={currentPage === maxIndex}
|
||||
onClick={() => {
|
||||
if (index >= maxIndex) {
|
||||
setPageIndex(maxIndex);
|
||||
paginate(size, maxIndex);
|
||||
} else {
|
||||
setPageIndex(index + 1);
|
||||
paginate(size, index + 1);
|
||||
}
|
||||
const pgIndex = size + index;
|
||||
setPageIndex(pgIndex);
|
||||
setCurrentPage(currentPage + 1);
|
||||
paginate(size, pgIndex);
|
||||
}}
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0"
|
||||
|
|
@ -100,11 +102,13 @@ export default function PaginatorComponent({
|
|||
<IconComponent name="ChevronRight" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={currentPage === maxIndex}
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => {
|
||||
setPageIndex(maxIndex);
|
||||
paginate(size, maxIndex);
|
||||
setPageIndex(maxIndex - 1);
|
||||
setCurrentPage(maxIndex);
|
||||
paginate(size, size);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
|
|
|
|||
30
src/frontend/src/components/authAdminGuard/index.tsx
Normal file
30
src/frontend/src/components/authAdminGuard/index.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { useContext, useEffect } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
export const ProtectedAdminRoute = ({ children }) => {
|
||||
const {
|
||||
isAdmin,
|
||||
isAuthenticated,
|
||||
logout,
|
||||
getAuthentication,
|
||||
userData,
|
||||
autoLogin,
|
||||
} = useContext(AuthContext);
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated && !getAuthentication()) {
|
||||
window.location.replace("/login");
|
||||
logout();
|
||||
}
|
||||
}, [isAuthenticated, getAuthentication, logout, userData]);
|
||||
|
||||
if (!isAuthenticated && !getAuthentication()) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
if ((userData && !isAdmin) || autoLogin) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
14
src/frontend/src/components/authGuard/index.tsx
Normal file
14
src/frontend/src/components/authGuard/index.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useContext } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
export const ProtectedRoute = ({ children }) => {
|
||||
const { isAuthenticated, logout, getAuthentication } =
|
||||
useContext(AuthContext);
|
||||
if (!isAuthenticated && !getAuthentication()) {
|
||||
logout();
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
19
src/frontend/src/components/authLoginGuard/index.tsx
Normal file
19
src/frontend/src/components/authLoginGuard/index.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { useContext } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
export const ProtectedLoginRoute = ({ children }) => {
|
||||
const { getAuthentication, autoLogin } = useContext(AuthContext);
|
||||
|
||||
if (autoLogin === true) {
|
||||
window.location.replace("/");
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
if (getAuthentication()) {
|
||||
window.location.replace("/");
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
13
src/frontend/src/components/catchAllRoutes/index.tsx
Normal file
13
src/frontend/src/components/catchAllRoutes/index.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const CatchAllRoute = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Redirect to the root ("/") when the catch-all route is matched
|
||||
useEffect(() => {
|
||||
navigate("/");
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
@ -60,6 +60,11 @@ export default function BuildTrigger({
|
|||
],
|
||||
});
|
||||
}
|
||||
if (errors.length === 0 && allNodesValid) {
|
||||
setSuccessData({
|
||||
title: "Flow is ready to run",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -28,8 +28,11 @@ import {
|
|||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "../../components/ui/tabs";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { darkContext } from "../../contexts/darkContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { codeTabsPropsType } from "../../types/components";
|
||||
import { unselectAllNodes } from "../../utils/reactflowUtils";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
|
||||
|
|
@ -45,6 +48,8 @@ export default function CodeTabsComponent({
|
|||
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
|
||||
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
|
||||
const { dark } = useContext(darkContext);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const { isTweakPage, setIsTweakPage } = useContext(alertContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (flow && flow["data"]!["nodes"]) {
|
||||
|
|
@ -52,6 +57,19 @@ export default function CodeTabsComponent({
|
|||
}
|
||||
}, [flow]);
|
||||
|
||||
useEffect(() => {
|
||||
unselectAllNodes({
|
||||
data,
|
||||
updateNodes: (nodes) => {
|
||||
reactFlowInstance?.setNodes(nodes);
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (isTweakPage) setIsTweakPage(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
return;
|
||||
|
|
@ -159,13 +177,13 @@ export default function CodeTabsComponent({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{tabs.map((tab, index) => (
|
||||
{tabs.map((tab, idx) => (
|
||||
<TabsContent
|
||||
value={index.toString()}
|
||||
value={idx.toString()}
|
||||
className="api-modal-tabs-content"
|
||||
key={index} // Remember to add a unique key prop
|
||||
key={idx} // Remember to add a unique key prop
|
||||
>
|
||||
{index < 4 ? (
|
||||
{idx < 4 ? (
|
||||
<>
|
||||
{tab.description && (
|
||||
<div
|
||||
|
|
@ -181,7 +199,7 @@ export default function CodeTabsComponent({
|
|||
{tab.code}
|
||||
</SyntaxHighlighter>
|
||||
</>
|
||||
) : index === 4 ? (
|
||||
) : idx === 4 ? (
|
||||
<>
|
||||
<div className="api-modal-according-display">
|
||||
<div
|
||||
|
|
@ -192,8 +210,8 @@ export default function CodeTabsComponent({
|
|||
: "overflow-hidden"
|
||||
)}
|
||||
>
|
||||
{data?.map((node: any, index) => (
|
||||
<div className="px-3" key={index}>
|
||||
{data?.map((node: any, i) => (
|
||||
<div className="px-3" key={i}>
|
||||
{tweaks?.tweaksList!.current.includes(
|
||||
node["data"]["id"]
|
||||
) && (
|
||||
|
|
@ -236,10 +254,10 @@ export default function CodeTabsComponent({
|
|||
node.data.node.template[templateField]
|
||||
.type === "int")
|
||||
)
|
||||
.map((templateField, index) => {
|
||||
.map((templateField, indx) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={index}
|
||||
key={indx}
|
||||
className="h-10 dark:border-b-muted"
|
||||
>
|
||||
<TableCell className="p-0 text-center text-sm text-foreground">
|
||||
|
|
@ -278,7 +296,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
|
|
@ -327,7 +345,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
|
|
@ -372,7 +390,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
|
|
@ -405,7 +423,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = e;
|
||||
|
|
@ -496,7 +514,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
|
|
@ -532,7 +550,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
|
|
@ -584,7 +602,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
|
|
@ -639,7 +657,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
|
|
@ -694,7 +712,7 @@ export default function CodeTabsComponent({
|
|||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList![
|
||||
index
|
||||
i
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
|
|
|
|||
16
src/frontend/src/components/fetchErrorComponent/index.tsx
Normal file
16
src/frontend/src/components/fetchErrorComponent/index.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { fetchErrorComponentType } from "../../types/components";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
|
||||
export default function FetchErrorComponent({
|
||||
message,
|
||||
description,
|
||||
}: fetchErrorComponentType) {
|
||||
return (
|
||||
<div role="status" className="m-auto flex flex-col items-center">
|
||||
<IconComponent className={`h-16 w-16`} name="Unplug"></IconComponent>
|
||||
<br></br>
|
||||
<span className="text-lg text-almost-medium-blue">{message}</span>
|
||||
<span className="text-lg text-almost-medium-blue">{description}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
import { forwardRef } from "react";
|
||||
import { IconComponentProps } from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
|
||||
export default function IconComponent({
|
||||
name,
|
||||
className,
|
||||
iconColor,
|
||||
}: IconComponentProps): JSX.Element {
|
||||
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
||||
return (
|
||||
<TargetIcon
|
||||
strokeWidth={1.5}
|
||||
className={className}
|
||||
style={{ color: iconColor }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const ForwardedIconComponent = forwardRef(
|
||||
({ name, className, iconColor }: IconComponentProps, ref) => {
|
||||
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
||||
return (
|
||||
<TargetIcon
|
||||
strokeWidth={1.5}
|
||||
className={className}
|
||||
style={iconColor ? { color: iconColor } : {}}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ForwardedIconComponent;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { useContext, useEffect, useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import AlertDropdown from "../../alerts/alertDropDown";
|
||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { darkContext } from "../../contexts/darkContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { getRepoStars } from "../../controllers/API";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { Button } from "../ui/button";
|
||||
import { Separator } from "../ui/separator";
|
||||
|
|
@ -17,29 +17,54 @@ export default function Header(): JSX.Element {
|
|||
const { dark, setDark } = useContext(darkContext);
|
||||
const { notificationCenter } = useContext(alertContext);
|
||||
const location = useLocation();
|
||||
const { logout, autoLogin, isAdmin } = useContext(AuthContext);
|
||||
const { stars } = useContext(darkContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [stars, setStars] = useState(null);
|
||||
|
||||
// Get and set numbers of stars on header
|
||||
useEffect(() => {
|
||||
async function fetchStars() {
|
||||
const starsCount = await getRepoStars("logspace-ai", "langflow");
|
||||
setStars(starsCount);
|
||||
}
|
||||
fetchStars();
|
||||
}, []);
|
||||
return (
|
||||
<div className="header-arrangement">
|
||||
<div className="header-start-display">
|
||||
<Link to="/">
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
<Button variant="outline" className="">
|
||||
Sign out
|
||||
</Button>
|
||||
|
||||
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
|
||||
<MenuBar flows={flows} tabId={tabId} />
|
||||
)}
|
||||
{!autoLogin && location.pathname !== `/flow/${tabId}` && (
|
||||
<a
|
||||
onClick={() => {
|
||||
logout();
|
||||
navigate("/login");
|
||||
}}
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer mx-5"
|
||||
>
|
||||
Sign out
|
||||
</a>
|
||||
)}
|
||||
|
||||
{location.pathname === "/admin" && (
|
||||
<a
|
||||
onClick={() => {
|
||||
navigate("/");
|
||||
}}
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
)}
|
||||
|
||||
{isAdmin &&
|
||||
!autoLogin &&
|
||||
location.pathname !== "/admin" &&
|
||||
location.pathname !== `/flow/${tabId}` && (
|
||||
<a
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer"
|
||||
onClick={() => navigate("/admin")}
|
||||
>
|
||||
Admin page
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="round-button-div">
|
||||
<Link to="/">
|
||||
|
|
@ -119,6 +144,18 @@ export default function Header(): JSX.Element {
|
|||
/>
|
||||
</div>
|
||||
</AlertDropdown>
|
||||
{!autoLogin && (
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate("/account/api-keys");
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name="Key"
|
||||
className="side-bar-button-size text-muted-foreground hover:text-accent-foreground"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ export default function InputComponent({
|
|||
? "input-component-true-button"
|
||||
: "input-component-false-button"
|
||||
)}
|
||||
onClick={() => {
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setPwdVisible(!pwdVisible);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -508,6 +508,7 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
|
|||
"/api/v1/validate/code",
|
||||
"/api/v1/custom_component",
|
||||
"/api/v1/validate/prompt",
|
||||
"http://localhost:7860/login",
|
||||
];
|
||||
|
||||
export const skipNodeUpdate = ["CustomComponent"];
|
||||
|
|
@ -522,6 +523,18 @@ export const CONTROL_LOGIN_STATE = {
|
|||
username: "",
|
||||
password: "",
|
||||
};
|
||||
|
||||
export const CONTROL_NEW_USER = {
|
||||
username: "",
|
||||
password: "",
|
||||
is_active: false,
|
||||
is_superuser: false,
|
||||
};
|
||||
|
||||
export const CONTROL_NEW_API_KEY = {
|
||||
apikeyname: "",
|
||||
};
|
||||
|
||||
export const tabsCode = [];
|
||||
|
||||
export function tabsArray(codes: string[], method: number) {
|
||||
|
|
@ -602,3 +615,24 @@ export function tabsArray(codes: string[], method: number) {
|
|||
},
|
||||
];
|
||||
}
|
||||
export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection.";
|
||||
export const FETCH_ERROR_DESCRIPION =
|
||||
"Check if everything is working properly and try again.";
|
||||
|
||||
export const BASE_URL_API = "/api/v1/";
|
||||
|
||||
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_USER_KEYS =
|
||||
"This user does not have any keys assigned at the moment.";
|
||||
|
||||
export const LAST_USED_SPAN_1 = "The last time this key was used.";
|
||||
|
||||
export const LAST_USED_SPAN_2 =
|
||||
"Accurate to within the hour from the most recent usage.";
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ const initialValue: alertContextType = {
|
|||
pushNotificationList: () => {},
|
||||
clearNotificationList: () => {},
|
||||
removeFromNotificationList: () => {},
|
||||
isTweakPage: false,
|
||||
setIsTweakPage: () => {},
|
||||
};
|
||||
|
||||
export const alertContext = createContext<alertContextType>(initialValue);
|
||||
|
|
@ -48,6 +50,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
|||
const [successOpen, setSuccessOpen] = useState(false);
|
||||
const [notificationCenter, setNotificationCenter] = useState(false);
|
||||
const [notificationList, setNotificationList] = useState<AlertItemType[]>([]);
|
||||
const [isTweakPage, setIsTweakPage] = useState<boolean>(false);
|
||||
const pushNotificationList = (notification: AlertItemType) => {
|
||||
setNotificationList((old) => {
|
||||
let newNotificationList = _.cloneDeep(old);
|
||||
|
|
@ -120,6 +123,8 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
|||
return (
|
||||
<alertContext.Provider
|
||||
value={{
|
||||
isTweakPage,
|
||||
setIsTweakPage,
|
||||
removeFromNotificationList,
|
||||
clearNotificationList,
|
||||
notificationList,
|
||||
|
|
|
|||
|
|
@ -1,74 +1,120 @@
|
|||
import { createContext, useEffect, useState } from "react";
|
||||
import { AuthContextType, userData } from "../types/contexts/auth";
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import Cookies from "universal-cookie";
|
||||
import { autoLogin as autoLoginApi, getLoggedUser } from "../controllers/API";
|
||||
import { Users } from "../types/api";
|
||||
import { AuthContextType } from "../types/contexts/auth";
|
||||
import { alertContext } from "./alertContext";
|
||||
|
||||
const initialValue: AuthContextType = {
|
||||
isAdmin: false,
|
||||
setIsAdmin: () => false,
|
||||
isAuthenticated: false,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
refreshAccessToken: () => Promise.resolve(),
|
||||
userData: null,
|
||||
setUserData: () => {},
|
||||
getAuthentication: () => false,
|
||||
authenticationErrorCount: 0,
|
||||
autoLogin: false,
|
||||
setAutoLogin: () => {},
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextType>(initialValue);
|
||||
export const AuthContext = createContext<AuthContextType>(initialValue);
|
||||
|
||||
export function AuthProvider({ children }): React.ReactElement {
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
const [userData, setUserData] = useState<userData | null>(null);
|
||||
|
||||
const cookies = new Cookies();
|
||||
const [accessToken, setAccessToken] = useState<string | null>(
|
||||
cookies.get("access_token")
|
||||
);
|
||||
const [refreshToken, setRefreshToken] = useState<string | null>(
|
||||
cookies.get("refresh_token")
|
||||
);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
||||
const [userData, setUserData] = useState<Users | null>(null);
|
||||
const [autoLogin, setAutoLogin] = useState<boolean>(false);
|
||||
const { setLoading } = useContext(alertContext);
|
||||
useEffect(() => {
|
||||
const storedAccessToken = localStorage.getItem("access_token");
|
||||
const storedAccessToken = cookies.get("access_token");
|
||||
if (storedAccessToken) {
|
||||
setAccessToken(storedAccessToken);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const isLoginPage = location.pathname.includes("login");
|
||||
|
||||
autoLoginApi()
|
||||
.then((user) => {
|
||||
if (user && user["access_token"]) {
|
||||
user["refresh_token"] = "auto";
|
||||
login(user["access_token"], user["refresh_token"]);
|
||||
setUserData(user);
|
||||
setAutoLogin(true);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setAutoLogin(false);
|
||||
if (getAuthentication() && !isLoginPage) {
|
||||
getLoggedUser()
|
||||
.then((user) => {
|
||||
setUserData(user);
|
||||
setLoading(false);
|
||||
const isSuperUser = user.is_superuser;
|
||||
setIsAdmin(isSuperUser);
|
||||
})
|
||||
.catch((error) => {});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
function getAuthentication() {
|
||||
const storedRefreshToken = cookies.get("refresh_token");
|
||||
const storedAccess = cookies.get("access_token");
|
||||
const auth = storedAccess && storedRefreshToken ? true : false;
|
||||
return auth;
|
||||
}
|
||||
|
||||
function login(newAccessToken: string, refreshToken: string) {
|
||||
localStorage.setItem("access_token", newAccessToken);
|
||||
cookies.set("access_token", newAccessToken, { path: "/" });
|
||||
cookies.set("refresh_token", refreshToken, { path: "/" });
|
||||
setAccessToken(newAccessToken);
|
||||
// Store refreshToken if needed
|
||||
setRefreshToken(refreshToken);
|
||||
setIsAuthenticated(true);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem("access_token");
|
||||
// Clear refreshToken if used
|
||||
cookies.remove("access_token", { path: "/" });
|
||||
cookies.remove("refresh_token", { path: "/" });
|
||||
setIsAdmin(false);
|
||||
setUserData(null);
|
||||
setAccessToken(null);
|
||||
}
|
||||
|
||||
async function refreshAccessToken(refreshToken: string) {
|
||||
try {
|
||||
// Call your API to refresh the access token using the refresh token
|
||||
const response = await fetch("/api/refresh-token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
login(data.accessToken, refreshToken);
|
||||
} else {
|
||||
logout();
|
||||
}
|
||||
} catch (error) {
|
||||
logout();
|
||||
}
|
||||
setRefreshToken(null);
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
|
||||
return (
|
||||
// !! to convert string to boolean
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
isAdmin,
|
||||
setIsAdmin,
|
||||
isAuthenticated: !!accessToken,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
login,
|
||||
logout,
|
||||
refreshAccessToken,
|
||||
setUserData,
|
||||
userData,
|
||||
getAuthentication,
|
||||
authenticationErrorCount: 0,
|
||||
setAutoLogin,
|
||||
autoLogin,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { createContext, useEffect, useState } from "react";
|
||||
import { getRepoStars } from "../controllers/API";
|
||||
import { darkContextType } from "../types/typesContext";
|
||||
|
||||
const initialValue = {
|
||||
dark: {},
|
||||
setDark: () => {},
|
||||
stars: 0,
|
||||
setStars: (stars) => 0,
|
||||
};
|
||||
|
||||
export const darkContext = createContext<darkContextType>(initialValue);
|
||||
|
|
@ -12,6 +15,16 @@ export function DarkProvider({ children }) {
|
|||
const [dark, setDark] = useState(
|
||||
JSON.parse(window.localStorage.getItem("isDark")!) ?? false
|
||||
);
|
||||
const [stars, setStars] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchStars() {
|
||||
const starsCount = await getRepoStars("logspace-ai", "langflow");
|
||||
setStars(starsCount);
|
||||
}
|
||||
fetchStars();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dark) {
|
||||
document.getElementById("body")!.classList.add("dark");
|
||||
|
|
@ -20,9 +33,12 @@ export function DarkProvider({ children }) {
|
|||
}
|
||||
window.localStorage.setItem("isDark", dark.toString());
|
||||
}, [dark]);
|
||||
|
||||
return (
|
||||
<darkContext.Provider
|
||||
value={{
|
||||
setStars,
|
||||
stars,
|
||||
dark,
|
||||
setDark,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import { ReactNode } from "react";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { ReactFlowProvider } from "reactflow";
|
||||
import { TooltipProvider } from "../components/ui/tooltip";
|
||||
import { ApiInterceptor } from "../controllers/API/api";
|
||||
import { SSEProvider } from "./SSEContext";
|
||||
import { AlertProvider } from "./alertContext";
|
||||
import { AuthProvider } from "./authContext";
|
||||
import { DarkProvider } from "./darkContext";
|
||||
import { LocationProvider } from "./locationContext";
|
||||
import { TabsProvider } from "./tabsContext";
|
||||
|
|
@ -13,23 +16,28 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
|
|||
//element to wrap all context
|
||||
return (
|
||||
<>
|
||||
<TooltipProvider>
|
||||
<ReactFlowProvider>
|
||||
<DarkProvider>
|
||||
<AlertProvider>
|
||||
<TypesProvider>
|
||||
<LocationProvider>
|
||||
<SSEProvider>
|
||||
<TabsProvider>
|
||||
<UndoRedoProvider>{children}</UndoRedoProvider>
|
||||
</TabsProvider>
|
||||
</SSEProvider>
|
||||
</LocationProvider>
|
||||
</TypesProvider>
|
||||
</AlertProvider>
|
||||
</DarkProvider>
|
||||
</ReactFlowProvider>
|
||||
</TooltipProvider>
|
||||
<BrowserRouter>
|
||||
<AlertProvider>
|
||||
<AuthProvider>
|
||||
<TooltipProvider>
|
||||
<ReactFlowProvider>
|
||||
<DarkProvider>
|
||||
<TypesProvider>
|
||||
<LocationProvider>
|
||||
<ApiInterceptor />
|
||||
<SSEProvider>
|
||||
<TabsProvider>
|
||||
<UndoRedoProvider>{children}</UndoRedoProvider>
|
||||
</TabsProvider>
|
||||
</SSEProvider>
|
||||
</LocationProvider>
|
||||
</TypesProvider>
|
||||
</DarkProvider>
|
||||
</ReactFlowProvider>
|
||||
</TooltipProvider>
|
||||
</AuthProvider>
|
||||
</AlertProvider>
|
||||
</BrowserRouter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { AxiosError } from "axios";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
ReactNode,
|
||||
|
|
@ -27,7 +28,7 @@ import {
|
|||
sourceHandleType,
|
||||
targetHandleType,
|
||||
} from "../types/flow";
|
||||
import { TabsContextType, TabsState, errorsVarType } from "../types/tabs";
|
||||
import { TabsContextType, TabsState } from "../types/tabs";
|
||||
import {
|
||||
addVersionToDuplicates,
|
||||
checkOldEdgesHandles,
|
||||
|
|
@ -39,6 +40,7 @@ import {
|
|||
} from "../utils/reactflowUtils";
|
||||
import { getRandomDescription, getRandomName } from "../utils/utils";
|
||||
import { alertContext } from "./alertContext";
|
||||
import { AuthContext } from "./authContext";
|
||||
import { typesContext } from "./typesContext";
|
||||
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
|
|
@ -78,7 +80,9 @@ export const TabsContext = createContext<TabsContextType>(
|
|||
);
|
||||
|
||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||
const { setErrorData, setNoticeData, setSuccessData } =
|
||||
useContext(alertContext);
|
||||
const { getAuthentication } = useContext(AuthContext);
|
||||
|
||||
const [tabId, setTabId] = useState("");
|
||||
|
||||
|
|
@ -127,24 +131,26 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
try {
|
||||
processDBData(DbData);
|
||||
updateStateWithDbData(DbData);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// get data from db
|
||||
//get tabs locally saved
|
||||
// let tabsData = getLocalStorageTabsData();
|
||||
refreshFlows();
|
||||
}, [templates]);
|
||||
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
|
||||
if (getAuthentication() === true) {
|
||||
// get data from db
|
||||
//get tabs locally saved
|
||||
// let tabsData = getLocalStorageTabsData();
|
||||
refreshFlows();
|
||||
}
|
||||
}, [templates, getAuthentication()]);
|
||||
|
||||
function getTabsDataFromDB() {
|
||||
//get tabs from db
|
||||
return readFlowsFromDatabase();
|
||||
}
|
||||
|
||||
function processDBData(DbData: FlowType[]) {
|
||||
DbData.forEach((flow: FlowType) => {
|
||||
try {
|
||||
|
|
@ -153,9 +159,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
processFlowEdges(flow);
|
||||
processFlowNodes(flow);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -497,7 +501,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
return id;
|
||||
} catch (error) {
|
||||
// Handle the error if needed
|
||||
console.error("Error while adding flow:", error);
|
||||
throw error; // Re-throw the error so the caller can handle it if needed
|
||||
}
|
||||
} else {
|
||||
|
|
@ -603,6 +606,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
const updatedFlow = await updateFlowInDatabase(newFlow);
|
||||
if (updatedFlow) {
|
||||
// updates flow in state
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||
|
|
@ -625,7 +629,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}
|
||||
} catch (err) {
|
||||
setErrorData(err as errorsVarType);
|
||||
setErrorData({
|
||||
title: "Error while saving changes",
|
||||
list: [(err as AxiosError).message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import {
|
|||
useState,
|
||||
} from "react";
|
||||
import { Node, ReactFlowInstance } from "reactflow";
|
||||
import { getAll } from "../controllers/API";
|
||||
import { getAll, getHealth } from "../controllers/API";
|
||||
import { APIKindType } from "../types/api";
|
||||
import { typesContextType } from "../types/typesContext";
|
||||
import { alertContext } from "./alertContext";
|
||||
import { AuthContext } from "./authContext";
|
||||
|
||||
//context to share types adn functions from nodes to flow
|
||||
|
||||
|
|
@ -23,6 +24,8 @@ const initialValue: typesContextType = {
|
|||
setTemplates: () => {},
|
||||
data: {},
|
||||
setData: () => {},
|
||||
setFetchError: () => {},
|
||||
fetchError: false,
|
||||
};
|
||||
|
||||
export const typesContext = createContext<typesContextType>(initialValue);
|
||||
|
|
@ -33,67 +36,58 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
|||
useState<ReactFlowInstance | null>(null);
|
||||
const [templates, setTemplates] = useState({});
|
||||
const [data, setData] = useState({});
|
||||
const [fetchError, setFetchError] = useState(false);
|
||||
const { setLoading } = useContext(alertContext);
|
||||
const { getAuthentication } = useContext(AuthContext);
|
||||
|
||||
useEffect(() => {
|
||||
let delay = 1000; // Start delay of 1 second
|
||||
let intervalId: NodeJS.Timer;
|
||||
let retryCount = 0; // Count of retry attempts
|
||||
const maxRetryCount = 5; // Max retry attempts
|
||||
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
|
||||
if (getAuthentication() === true) {
|
||||
getTypes();
|
||||
}
|
||||
}, [getAuthentication()]);
|
||||
|
||||
async function getTypes(): Promise<void> {
|
||||
// We will keep a flag to handle the case where the component is unmounted before the API call resolves.
|
||||
let isMounted = true;
|
||||
|
||||
async function getTypes(): Promise<void> {
|
||||
try {
|
||||
const result = await getAll();
|
||||
// Make sure to only update the state if the component is still mounted.
|
||||
if (isMounted) {
|
||||
setLoading(false);
|
||||
setData(result.data);
|
||||
setTemplates(
|
||||
Object.keys(result.data).reduce((acc, curr) => {
|
||||
try {
|
||||
const result = await getAll();
|
||||
// Make sure to only update the state if the component is still mounted.
|
||||
if (isMounted && result?.status === 200) {
|
||||
setLoading(false);
|
||||
setData(result.data);
|
||||
setTemplates(
|
||||
Object.keys(result.data).reduce((acc, curr) => {
|
||||
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
|
||||
acc[c] = result.data[curr][c];
|
||||
});
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||
setTypes(
|
||||
// Reverse the keys so the tool world does not overlap
|
||||
Object.keys(result.data)
|
||||
.reverse()
|
||||
.reduce((acc, curr) => {
|
||||
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
|
||||
acc[c] = result.data[curr][c];
|
||||
acc[c] = curr;
|
||||
// Add the base classes to the accumulator as well.
|
||||
result.data[curr][c].base_classes?.forEach((b) => {
|
||||
acc[b] = curr;
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||
setTypes(
|
||||
// Reverse the keys so the tool world does not overlap
|
||||
Object.keys(result.data)
|
||||
.reverse()
|
||||
.reduce((acc, curr) => {
|
||||
Object.keys(result.data[curr]).forEach(
|
||||
(c: keyof APIKindType) => {
|
||||
acc[c] = curr;
|
||||
// Add the base classes to the accumulator as well.
|
||||
result.data[curr][c].base_classes?.forEach((b) => {
|
||||
acc[b] = curr;
|
||||
});
|
||||
}
|
||||
);
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
}
|
||||
// Clear the interval if successful.
|
||||
clearInterval(intervalId!);
|
||||
} catch (error) {
|
||||
console.error("An error has occurred while fetching types.");
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("An error has occurred while fetching types.");
|
||||
await getHealth().catch((e) => {
|
||||
setFetchError(true);
|
||||
});
|
||||
}
|
||||
|
||||
// Start the initial interval.
|
||||
intervalId = setInterval(getTypes, delay);
|
||||
return () => {
|
||||
// This will clear the interval when the component unmounts, or when the dependencies of the useEffect hook change.
|
||||
clearInterval(intervalId!);
|
||||
// Indicate that the component has been unmounted.
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
function deleteNode(idx: string) {
|
||||
reactFlowInstance!.setNodes(
|
||||
|
|
@ -117,6 +111,8 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
|||
templates,
|
||||
data,
|
||||
setData,
|
||||
fetchError,
|
||||
setFetchError,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,117 @@
|
|||
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import { useContext, useEffect } from "react";
|
||||
import { Cookies } from "react-cookie";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { renewAccessToken } from ".";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
// Create a new Axios instance
|
||||
const api: AxiosInstance = axios.create({
|
||||
baseURL: "",
|
||||
});
|
||||
|
||||
function ApiInterceptor(): null {
|
||||
const retryCounts = useRef([]);
|
||||
function ApiInterceptor() {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
let { accessToken, login, logout, authenticationErrorCount } =
|
||||
useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
const cookies = new Cookies();
|
||||
|
||||
useEffect(() => {
|
||||
const interceptor = api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// let retryCount = 0;
|
||||
// while (retryCount < 4) {
|
||||
// await sleep(5000); // Sleep for 5 seconds
|
||||
// retryCount++;
|
||||
// try {
|
||||
// const response = await axios.request(error.config);
|
||||
// return response;
|
||||
// } catch (error) {
|
||||
// if (retryCount === 3) {
|
||||
// setErrorData({
|
||||
// title: "There was an error on web connection, please: ",
|
||||
// list: [
|
||||
// "Refresh the page",
|
||||
// "Use a new flow tab",
|
||||
// "Check if the backend is up",
|
||||
// "Endpoint: " + error.config?.url,
|
||||
// ],
|
||||
// });
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if (error.response?.status === 401) {
|
||||
const refreshToken = cookies.get("refresh_token");
|
||||
if (refreshToken && refreshToken !== "auto") {
|
||||
authenticationErrorCount = authenticationErrorCount + 1;
|
||||
if (authenticationErrorCount > 3) {
|
||||
authenticationErrorCount = 0;
|
||||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
|
||||
const res = await renewAccessToken(refreshToken);
|
||||
login(res.data.access_token, res.data.refresh_token);
|
||||
try {
|
||||
if (error?.config?.headers) {
|
||||
delete error.config.headers["Authorization"];
|
||||
error.config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||
const response = await axios.request(error.config);
|
||||
return response;
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!refreshToken && error?.config?.url?.includes("login")) {
|
||||
return Promise.reject(error);
|
||||
} else {
|
||||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
} else {
|
||||
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
||||
return Promise.reject(error);
|
||||
// }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const isAuthorizedURL = (url) => {
|
||||
const authorizedDomains = [
|
||||
"https://raw.githubusercontent.com/logspace-ai/langflow_examples/main/examples",
|
||||
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples",
|
||||
"https://api.github.com/repos/logspace-ai/langflow",
|
||||
"auto_login",
|
||||
];
|
||||
|
||||
const authorizedEndpoints = ["auto_login"];
|
||||
|
||||
try {
|
||||
const parsedURL = new URL(url);
|
||||
|
||||
const isDomainAllowed = authorizedDomains.some(
|
||||
(domain) => parsedURL.origin === new URL(domain).origin
|
||||
);
|
||||
const isEndpointAllowed = authorizedEndpoints.some((endpoint) =>
|
||||
parsedURL.pathname.includes(endpoint)
|
||||
);
|
||||
|
||||
return isDomainAllowed || isEndpointAllowed;
|
||||
} catch (e) {
|
||||
// Invalid URL
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Request interceptor to add access token to every request
|
||||
const requestInterceptor = api.interceptors.request.use(
|
||||
(config) => {
|
||||
if (accessToken && !isAuthorizedURL(config?.url)) {
|
||||
config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
// Clean up the interceptor when the component unmounts
|
||||
// Clean up the interceptors when the component unmounts
|
||||
api.interceptors.response.eject(interceptor);
|
||||
api.interceptors.request.eject(requestInterceptor);
|
||||
};
|
||||
}, [retryCounts]);
|
||||
}, [accessToken, setErrorData]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Function to sleep for a given duration in milliseconds
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export { ApiInterceptor, api };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import { AxiosResponse } from "axios";
|
||||
import { ReactFlowJsonObject } from "reactflow";
|
||||
import { BASE_URL_API } from "../../constants/constants";
|
||||
import { api } from "../../controllers/API/api";
|
||||
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
||||
import {
|
||||
APIObjectType,
|
||||
LoginType,
|
||||
Users,
|
||||
sendAllProps,
|
||||
} from "../../types/api/index";
|
||||
import { UserInputType } from "../../types/components";
|
||||
import { FlowStyleType, FlowType } from "../../types/flow";
|
||||
import {
|
||||
APIClassType,
|
||||
|
|
@ -18,7 +25,7 @@ import {
|
|||
* @returns {Promise<AxiosResponse<APIObjectType>>} A promise that resolves to an AxiosResponse containing all the objects.
|
||||
*/
|
||||
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
||||
return await api.get(`/api/v1/all`);
|
||||
return await api.get(`${BASE_URL_API}all`);
|
||||
}
|
||||
|
||||
const GITHUB_API_URL = "https://api.github.com";
|
||||
|
|
@ -40,13 +47,13 @@ export async function getRepoStars(owner: string, repo: string) {
|
|||
* @returns {AxiosResponse<any>} The API response.
|
||||
*/
|
||||
export async function sendAll(data: sendAllProps) {
|
||||
return await api.post(`/api/v1/predict`, data);
|
||||
return await api.post(`${BASE_URL_API}predict`, data);
|
||||
}
|
||||
|
||||
export async function postValidateCode(
|
||||
code: string
|
||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||
return await api.post("/api/v1/validate/code", { code });
|
||||
return await api.post(`${BASE_URL_API}validate/code`, { code });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -61,7 +68,7 @@ export async function postValidatePrompt(
|
|||
template: string,
|
||||
frontend_node: APIClassType
|
||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||
return await api.post("/api/v1/validate/prompt", {
|
||||
return await api.post(`${BASE_URL_API}validate/prompt`, {
|
||||
name: name,
|
||||
template: template,
|
||||
frontend_node: frontend_node,
|
||||
|
|
@ -105,7 +112,7 @@ export async function saveFlowToDatabase(newFlow: {
|
|||
style?: FlowStyleType;
|
||||
}): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.post("/api/v1/flows/", {
|
||||
const response = await api.post(`${BASE_URL_API}flows/`, {
|
||||
name: newFlow.name,
|
||||
data: newFlow.data,
|
||||
description: newFlow.description,
|
||||
|
|
@ -131,7 +138,7 @@ export async function updateFlowInDatabase(
|
|||
updatedFlow: FlowType
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(`/api/v1/flows/${updatedFlow.id}`, {
|
||||
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
|
||||
name: updatedFlow.name,
|
||||
data: updatedFlow.data,
|
||||
description: updatedFlow.description,
|
||||
|
|
@ -155,8 +162,8 @@ export async function updateFlowInDatabase(
|
|||
*/
|
||||
export async function readFlowsFromDatabase() {
|
||||
try {
|
||||
const response = await api.get("/api/v1/flows/");
|
||||
if (response.status !== 200) {
|
||||
const response = await api.get(`${BASE_URL_API}flows/`);
|
||||
if (response?.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.data;
|
||||
|
|
@ -168,8 +175,8 @@ export async function readFlowsFromDatabase() {
|
|||
|
||||
export async function downloadFlowsFromDatabase() {
|
||||
try {
|
||||
const response = await api.get("/api/v1/flows/download/");
|
||||
if (response.status !== 200) {
|
||||
const response = await api.get(`${BASE_URL_API}flows/download/`);
|
||||
if (response?.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.data;
|
||||
|
|
@ -181,7 +188,7 @@ export async function downloadFlowsFromDatabase() {
|
|||
|
||||
export async function uploadFlowsToDatabase(flows: FormData) {
|
||||
try {
|
||||
const response = await api.post(`/api/v1/flows/upload/`, flows);
|
||||
const response = await api.post(`${BASE_URL_API}flows/upload/`, flows);
|
||||
|
||||
if (response.status !== 201) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
|
|
@ -202,7 +209,7 @@ export async function uploadFlowsToDatabase(flows: FormData) {
|
|||
*/
|
||||
export async function deleteFlowFromDatabase(flowId: string) {
|
||||
try {
|
||||
const response = await api.delete(`/api/v1/flows/${flowId}`);
|
||||
const response = await api.delete(`${BASE_URL_API}flows/${flowId}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -222,7 +229,7 @@ export async function deleteFlowFromDatabase(flowId: string) {
|
|||
*/
|
||||
export async function getFlowFromDatabase(flowId: number) {
|
||||
try {
|
||||
const response = await api.get(`/api/v1/flows/${flowId}`);
|
||||
const response = await api.get(`${BASE_URL_API}flows/${flowId}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -241,7 +248,7 @@ export async function getFlowFromDatabase(flowId: number) {
|
|||
*/
|
||||
export async function getFlowStylesFromDatabase() {
|
||||
try {
|
||||
const response = await api.get("/api/v1/flow_styles/");
|
||||
const response = await api.get(`${BASE_URL_API}flow_styles/`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -261,7 +268,7 @@ export async function getFlowStylesFromDatabase() {
|
|||
*/
|
||||
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
||||
try {
|
||||
const response = await api.post("/api/v1/flow_styles/", flowStyle, {
|
||||
const response = await api.post(`${BASE_URL_API}flow_styles/`, flowStyle, {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -284,7 +291,7 @@ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
|||
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the version information.
|
||||
*/
|
||||
export async function getVersion() {
|
||||
const respnose = await api.get("/api/v1/version");
|
||||
const respnose = await api.get(`${BASE_URL_API}version`);
|
||||
return respnose.data;
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +313,7 @@ export async function getHealth() {
|
|||
export async function getBuildStatus(
|
||||
flowId: string
|
||||
): Promise<BuildStatusTypeAPI> {
|
||||
return await api.get(`/api/v1/build/${flowId}/status`);
|
||||
return await api.get(`${BASE_URL_API}build/${flowId}/status`);
|
||||
}
|
||||
|
||||
//docs for postbuildinit
|
||||
|
|
@ -319,7 +326,7 @@ export async function getBuildStatus(
|
|||
export async function postBuildInit(
|
||||
flow: FlowType
|
||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||
return await api.post(`/api/v1/build/init/${flow.id}`, flow);
|
||||
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
|
||||
}
|
||||
|
||||
// fetch(`/upload/${id}`, {
|
||||
|
|
@ -337,12 +344,160 @@ export async function uploadFile(
|
|||
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return await api.post(`/api/v1/upload/${id}`, formData);
|
||||
return await api.post(`${BASE_URL_API}upload/${id}`, formData);
|
||||
}
|
||||
|
||||
export async function postCustomComponent(
|
||||
code: string,
|
||||
apiClass: APIClassType
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
return await api.post(`/api/v1/custom_component`, { code });
|
||||
return await api.post(`${BASE_URL_API}custom_component`, { code });
|
||||
}
|
||||
|
||||
export async function onLogin(user: LoginType) {
|
||||
try {
|
||||
const response = await api.post(
|
||||
`${BASE_URL_API}login`,
|
||||
new URLSearchParams({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
}).toString(),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.data;
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function autoLogin() {
|
||||
try {
|
||||
const response = await api.get(`${BASE_URL_API}auto_login`);
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.data;
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function renewAccessToken(token: string) {
|
||||
try {
|
||||
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLoggedUser(): Promise<Users> {
|
||||
try {
|
||||
const res = await api.get(`${BASE_URL_API}user`);
|
||||
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function addUser(user: UserInputType): Promise<Users> {
|
||||
try {
|
||||
const res = await api.post(`${BASE_URL_API}user`, user);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUsersPage(
|
||||
skip: number,
|
||||
limit: number
|
||||
): Promise<[Users]> {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`${BASE_URL_API}users?skip=${skip}&limit=${limit}`
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteUser(user_id: string) {
|
||||
try {
|
||||
const res = await api.delete(`${BASE_URL_API}user/${user_id}`);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateUser(user_id: string, user: Users) {
|
||||
try {
|
||||
const res = await api.patch(`${BASE_URL_API}user/${user_id}`, user);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getApiKey() {
|
||||
try {
|
||||
const res = await api.get(`${BASE_URL_API}api_key`);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createApiKey(name: string) {
|
||||
try {
|
||||
const res = await api.post(`${BASE_URL_API}api_key`, { name });
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteApiKey(api_key: string) {
|
||||
try {
|
||||
const res = await api.delete(`${BASE_URL_API}api_key/${api_key}`);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import ContextWrapper from "./contexts";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
|
||||
import { ApiInterceptor } from "./controllers/API/api";
|
||||
// @ts-ignore
|
||||
import "./style/index.css";
|
||||
// @ts-ignore
|
||||
|
|
@ -17,10 +15,7 @@ const root = ReactDOM.createRoot(
|
|||
);
|
||||
root.render(
|
||||
<ContextWrapper>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<ApiInterceptor />
|
||||
</BrowserRouter>
|
||||
<App />
|
||||
</ContextWrapper>
|
||||
);
|
||||
reportWebVitals();
|
||||
|
|
|
|||
202
src/frontend/src/modals/SecretKeyModal/index.tsx
Normal file
202
src/frontend/src/modals/SecretKeyModal/index.tsx
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
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 { createApiKey } from "../../controllers/API";
|
||||
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,
|
||||
onCloseModal,
|
||||
}: ApiKeyType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
const [open, setOpen] = useState(false);
|
||||
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
|
||||
const [apiKeyValue, setApiKeyValue] = useState("");
|
||||
const [inputState, setInputState] =
|
||||
useState<ApiKeyInputType>(CONTROL_NEW_API_KEY);
|
||||
const [renderKey, setRenderKey] = useState(false);
|
||||
const [textCopied, setTextCopied] = useState(true);
|
||||
const { setSuccessData } = useContext(alertContext);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setRenderKey(false);
|
||||
resetForm();
|
||||
} else {
|
||||
onCloseModal();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
function resetForm() {
|
||||
setApiKeyName("");
|
||||
setApiKeyValue("");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
function handleAddNewKey() {
|
||||
createApiKey(apiKeyName)
|
||||
.then((res) => {
|
||||
setApiKeyValue(res["api_key"]);
|
||||
})
|
||||
.catch((err) => {});
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseModal size="small-h-full" open={open} setOpen={setOpen}>
|
||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={""}>
|
||||
<span className="pr-2">{title}</span>
|
||||
<Icon
|
||||
name="icon"
|
||||
className="h-6 w-6 pl-1 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
{renderKey === true && (
|
||||
<>
|
||||
<span className="text-xs">
|
||||
Please save this secret key somewhere safe and accessible. For
|
||||
security reasons,{" "}
|
||||
<strong>you won't be able to view it again</strong> through your
|
||||
account. If you lose this secret key, you'll need to generate a
|
||||
new one.
|
||||
</span>
|
||||
<div className="flex pt-3">
|
||||
<div className="w-full">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
onChange={(event) => {
|
||||
setApiKeyValue(event.target.value);
|
||||
}}
|
||||
readOnly={true}
|
||||
value={apiKeyValue}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
className="ml-3"
|
||||
onClick={() => {
|
||||
handleCopyClick();
|
||||
}}
|
||||
>
|
||||
{textCopied ? (
|
||||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
) : (
|
||||
<IconComponent name="Check" className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
setRenderKey(true);
|
||||
handleAddNewKey();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{renderKey === false && (
|
||||
<div className="grid gap-5">
|
||||
<Form.Field name="username">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Name (optional){" "}
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "apikeyname", value } });
|
||||
setApiKeyName(value);
|
||||
}}
|
||||
value={apiKeyName}
|
||||
className="primary-input"
|
||||
placeholder="My key name"
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
</div>
|
||||
)}
|
||||
{renderKey === false && (
|
||||
<div className="float-right">
|
||||
<Button
|
||||
className="mr-3"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
|
||||
<Form.Submit asChild>
|
||||
<Button className="mt-8">{confirmationText}</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{renderKey === true && (
|
||||
<div className="float-right">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setRenderKey(false);
|
||||
}}
|
||||
className="mt-8"
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Form.Root>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +1,15 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { UserManagementType } from "../../types/components";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { CONTROL_NEW_USER } from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import {
|
||||
UserInputType,
|
||||
UserManagementType,
|
||||
inputHandlerEventType,
|
||||
} from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
|
|
@ -18,18 +25,32 @@ export default function UserManagementModal({
|
|||
onConfirm,
|
||||
}: UserManagementType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
const [confirmPwdVisible, setConfirmPwdVisible] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [password, setPassword] = useState(data?.password ?? "");
|
||||
const [username, setUserName] = useState(data?.user ?? "");
|
||||
const [username, setUserName] = useState(data?.username ?? "");
|
||||
const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
|
||||
const [isActive, setIsActive] = useState(data?.is_active ?? false);
|
||||
const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false);
|
||||
const [inputState, setInputState] = useState<UserInputType>(CONTROL_NEW_USER);
|
||||
const { userData } = useContext(AuthContext);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
resetForm();
|
||||
} else {
|
||||
handleInput({ target: { name: "username", value: username } });
|
||||
handleInput({ target: { name: "is_active", value: isActive } });
|
||||
handleInput({ target: { name: "is_superuser", value: isSuperUser } });
|
||||
}
|
||||
}, [data, open]);
|
||||
}, [open]);
|
||||
|
||||
function resetForm() {
|
||||
setPassword("");
|
||||
|
|
@ -55,10 +76,8 @@ export default function UserManagementModal({
|
|||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = Object.fromEntries(new FormData(event.currentTarget));
|
||||
resetForm();
|
||||
onConfirm(index ?? -1, data);
|
||||
onConfirm(1, inputState);
|
||||
setOpen(false);
|
||||
event.preventDefault();
|
||||
}}
|
||||
|
|
@ -79,8 +98,9 @@ export default function UserManagementModal({
|
|||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setUserName(input.target.value);
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "username", value } });
|
||||
setUserName(value);
|
||||
}}
|
||||
value={username}
|
||||
className="primary-input"
|
||||
|
|
@ -106,22 +126,40 @@ export default function UserManagementModal({
|
|||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
<Form.Label className="data-[invalid]:label-invalid flex">
|
||||
Password{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
<span className="ml-1 mr-1 font-medium text-destructive">
|
||||
*
|
||||
</span>
|
||||
{pwdVisible && (
|
||||
<Eye
|
||||
onClick={() => setPwdVisible(!pwdVisible)}
|
||||
className="h-5 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
)}
|
||||
{!pwdVisible && (
|
||||
<EyeOff
|
||||
onClick={() => setPwdVisible(!pwdVisible)}
|
||||
className="h-5 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
)}
|
||||
</Form.Label>
|
||||
</div>
|
||||
<InputComponent
|
||||
onChange={(input) => {
|
||||
setPassword(input);
|
||||
}}
|
||||
value={password}
|
||||
password={true}
|
||||
isForm
|
||||
className="primary-input"
|
||||
required
|
||||
placeholder="Password"
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
setPassword(value);
|
||||
}}
|
||||
value={password}
|
||||
className="primary-input"
|
||||
required={data ? false : true}
|
||||
type={pwdVisible ? "text" : "password"}
|
||||
/>
|
||||
</Form.Control>
|
||||
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter a password
|
||||
</Form.Message>
|
||||
|
|
@ -146,93 +184,108 @@ export default function UserManagementModal({
|
|||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
<Form.Label className="data-[invalid]:label-invalid flex">
|
||||
Confirm password{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
<span className="ml-1 mr-1 font-medium text-destructive">
|
||||
*
|
||||
</span>
|
||||
{confirmPwdVisible && (
|
||||
<Eye
|
||||
onClick={() =>
|
||||
setConfirmPwdVisible(!confirmPwdVisible)
|
||||
}
|
||||
className="h-5 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
)}
|
||||
{!confirmPwdVisible && (
|
||||
<EyeOff
|
||||
onClick={() =>
|
||||
setConfirmPwdVisible(!confirmPwdVisible)
|
||||
}
|
||||
className="h-5 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
)}
|
||||
</Form.Label>
|
||||
</div>
|
||||
<InputComponent
|
||||
onChange={(input) => {
|
||||
setConfirmPassword(input);
|
||||
}}
|
||||
value={confirmPassword}
|
||||
password={true}
|
||||
isForm
|
||||
className="primary-input"
|
||||
required
|
||||
placeholder="Confirm your password"
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setConfirmPassword(input.target.value);
|
||||
}}
|
||||
value={confirmPassword}
|
||||
className="primary-input"
|
||||
required={data ? false : true}
|
||||
type={confirmPwdVisible ? "text" : "password"}
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please confirm your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
<Form.Field name="email">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Email <span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter your email
|
||||
</Form.Message>
|
||||
<Form.Message className="field-invalid" match="typeMismatch">
|
||||
Please provide a valid email
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input className="primary-input" type="email" required />
|
||||
</Form.Control>
|
||||
</Form.Field> */}
|
||||
|
||||
{/*
|
||||
<Form.Field name="birth">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Date of birth{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter your date of birth
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
type="date"
|
||||
className="primary-input"
|
||||
required
|
||||
max={new Date().toISOString().split("T")[0]}
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Field> */}
|
||||
<div className="flex gap-8">
|
||||
<Form.Field name="is_active">
|
||||
<div>
|
||||
<Form.Label className="data-[invalid]:label-invalid mr-3">
|
||||
Active
|
||||
</Form.Label>
|
||||
<Form.Control asChild>
|
||||
<Checkbox
|
||||
value={isActive}
|
||||
checked={isActive}
|
||||
id="is_active"
|
||||
className="relative top-0.5"
|
||||
onCheckedChange={(value) => {
|
||||
handleInput({ target: { name: "is_active", value } });
|
||||
setIsActive(value);
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
</div>
|
||||
</Form.Field>
|
||||
{userData?.is_superuser && (
|
||||
<Form.Field name="is_superuser">
|
||||
<div>
|
||||
<Form.Label className="data-[invalid]:label-invalid mr-3">
|
||||
Superuser
|
||||
</Form.Label>
|
||||
<Form.Control asChild>
|
||||
<Checkbox
|
||||
checked={isSuperUser}
|
||||
value={isSuperUser}
|
||||
id="is_superuser"
|
||||
className="relative top-0.5"
|
||||
onCheckedChange={(value) => {
|
||||
handleInput({
|
||||
target: { name: "is_superuser", value },
|
||||
});
|
||||
setIsSuperUser(value);
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
</div>
|
||||
</Form.Field>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="float-right">
|
||||
<Form.Submit asChild>
|
||||
<Button className="mr-3 mt-8">{confirmationText}</Button>
|
||||
</Form.Submit>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
className="mr-3"
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
|
||||
<Form.Submit asChild>
|
||||
<Button className="mt-8">{confirmationText}</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
</Form.Root>
|
||||
</BaseModal.Content>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ export default function CodeAreaModal({
|
|||
const { dark } = useContext(darkContext);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const [height, setHeight] = useState<string | null>(null);
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const { setErrorData, setSuccessData, isTweakPage } =
|
||||
useContext(alertContext);
|
||||
const [error, setError] = useState<{
|
||||
detail: { error: string | undefined; traceback: string | undefined };
|
||||
} | null>(null);
|
||||
|
|
@ -39,7 +40,6 @@ export default function CodeAreaModal({
|
|||
if (dynamic && Object.keys(nodeClass!.template).length > 2) {
|
||||
return;
|
||||
}
|
||||
processCode();
|
||||
}, []);
|
||||
|
||||
function processNonDynamicField() {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
|||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { FlowSettingsPropsType } from "../../types/components";
|
||||
import BaseModal from "../baseModal";
|
||||
|
|
@ -12,15 +11,14 @@ export default function FlowSettingsModal({
|
|||
open,
|
||||
setOpen,
|
||||
}: FlowSettingsPropsType): JSX.Element {
|
||||
const { setSuccessData } = useContext(alertContext);
|
||||
const { flows, tabId, updateFlow, saveFlow } = useContext(TabsContext);
|
||||
const flow = flows.find((f) => f.id === tabId);
|
||||
useEffect(() => {
|
||||
setName(flow.name);
|
||||
setDescription(flow.description);
|
||||
}, [flow.name, flow.description]);
|
||||
const [name, setName] = useState(flow.name);
|
||||
const [description, setDescription] = useState(flow.description);
|
||||
setName(flow!.name);
|
||||
setDescription(flow!.description);
|
||||
}, [flow!.name, flow!.description]);
|
||||
const [name, setName] = useState(flow!.name);
|
||||
const [description, setDescription] = useState(flow!.description);
|
||||
const [invalidName, setInvalidName] = useState(false);
|
||||
|
||||
function handleClick(): void {
|
||||
|
|
@ -28,7 +26,6 @@ export default function FlowSettingsModal({
|
|||
savedFlow!.name = name;
|
||||
savedFlow!.description = description;
|
||||
saveFlow(savedFlow!);
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
setOpen(false);
|
||||
}
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
} from "../../components/ui/dialog";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { TabsState } from "../../types/tabs";
|
||||
import { validateNodes } from "../../utils/reactflowUtils";
|
||||
|
|
@ -60,6 +61,7 @@ export default function FormModal({
|
|||
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const { accessToken } = useContext(AuthContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
|
|
@ -160,7 +162,7 @@ export default function FormModal({
|
|||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO improve check of user authentication
|
||||
function getWebSocketUrl(
|
||||
chatId: string,
|
||||
isDevelopment: boolean = false
|
||||
|
|
@ -173,7 +175,7 @@ export default function FormModal({
|
|||
|
||||
return `${
|
||||
isDevelopment ? "ws" : webSocketProtocol
|
||||
}://${host}${chatEndpoint}`;
|
||||
}://${host}${chatEndpoint}?token=${accessToken}`;
|
||||
}
|
||||
|
||||
function handleWsMessage(data: any) {
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@ export default function GenericModal({
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setIsEdit(true);
|
||||
return setErrorData({
|
||||
title: "There is something wrong with this prompt, please review it",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,62 @@
|
|||
import { useContext, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { Input } from "../../../components/ui/input";
|
||||
import { CONTROL_LOGIN_STATE } from "../../../constants/constants";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { AuthContext } from "../../../contexts/authContext";
|
||||
import { getLoggedUser, onLogin } from "../../../controllers/API";
|
||||
import { LoginType } from "../../../types/api";
|
||||
import {
|
||||
inputHandlerEventType,
|
||||
loginInputStateType,
|
||||
} from "../../../types/components";
|
||||
|
||||
export default function LoginAdminPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
function loginAdmin() {
|
||||
navigate("/admin/");
|
||||
const [inputState, setInputState] =
|
||||
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
|
||||
const { login, getAuthentication, setUserData } = useContext(AuthContext);
|
||||
|
||||
const { password, username } = inputState;
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
function signIn() {
|
||||
const user: LoginType = {
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
onLogin(user)
|
||||
.then((user) => {
|
||||
login(user.access_token, user.refresh_token);
|
||||
getUser();
|
||||
navigate("/admin/");
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error signing in",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getUser() {
|
||||
if (getAuthentication) {
|
||||
setTimeout(() => {
|
||||
getLoggedUser()
|
||||
.then((user) => {
|
||||
setUserData(user);
|
||||
})
|
||||
.catch((error) => {});
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -14,11 +64,24 @@ export default function LoginAdminPage() {
|
|||
<div className="flex w-72 flex-col items-center justify-center gap-2">
|
||||
<span className="mb-4 text-5xl">⛓️</span>
|
||||
<span className="mb-6 text-2xl font-semibold text-primary">Admin</span>
|
||||
<Input className="bg-background" placeholder="Email address" />
|
||||
<Input className="bg-background" placeholder="Password" />
|
||||
<Input
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "username", value } });
|
||||
}}
|
||||
className="bg-background"
|
||||
placeholder="Username"
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
}}
|
||||
className="bg-background"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
loginAdmin();
|
||||
signIn();
|
||||
}}
|
||||
variant="default"
|
||||
className="w-full"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import _ from "lodash";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { X } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import PaginatorComponent from "../../components/PaginatorComponent";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import {
|
||||
Table,
|
||||
|
|
@ -14,265 +15,202 @@ import {
|
|||
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 UserManagementModal from "../../modals/UserManagementModal";
|
||||
import { UserInputType } from "../../types/components";
|
||||
import Header from "../../components/headerComponent";
|
||||
import { Users } from "../../types/api";
|
||||
|
||||
export default function AdminPage() {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const [size, setPageSize] = useState(10);
|
||||
const [index, setPageIndex] = useState(1);
|
||||
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([
|
||||
{
|
||||
user: generateRandomString(50),
|
||||
email: generateRandomString(50) + "@example.com",
|
||||
password: generateRandomString(50),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
]);
|
||||
const userList = useRef([]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
getUsers();
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
const [filterUserList, setFilterUserList] = useState(userList.current);
|
||||
|
||||
function generateRandomString(length) {
|
||||
const characters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
result += characters.charAt(randomIndex);
|
||||
}
|
||||
return result;
|
||||
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 generateRandomDate() {
|
||||
const start = new Date(2010, 0, 1);
|
||||
const end = new Date();
|
||||
const randomTimestamp =
|
||||
start.getTime() + Math.random() * (end.getTime() - start.getTime());
|
||||
const randomDate = new Date(randomTimestamp);
|
||||
|
||||
const options = { year: "numeric", month: "short", day: "numeric" };
|
||||
return randomDate.toLocaleDateString("en-US");
|
||||
}
|
||||
|
||||
const [editUser, setEditUser] = useState(-1);
|
||||
const [editedUser, setEditedUser] = useState("");
|
||||
const [modalEditOpen, setModalEditOpen] = useState(false);
|
||||
const [modalDeleteOpen, setModalDeleteOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
resetFilter();
|
||||
}, []);
|
||||
|
||||
const handleInputChange = (event, index) => {
|
||||
const user = _.cloneDeepWith(userList.current);
|
||||
user[index].password = event.target.value;
|
||||
userList.current = user;
|
||||
|
||||
const userFilter = _.cloneDeepWith(filterUserList);
|
||||
userFilter[index].password = event.target.value;
|
||||
setFilterUserList(userFilter);
|
||||
|
||||
setEditedUser(event.target.value);
|
||||
};
|
||||
|
||||
function handleChangePagination(pageIndex: number, pageSize: number) {
|
||||
setPageIndex(pageIndex);
|
||||
setPageSize(pageSize);
|
||||
|
||||
const startIndex = (pageIndex - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const newList = userList.current.slice(startIndex, endIndex);
|
||||
|
||||
setFilterUserList(newList);
|
||||
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() {
|
||||
setFilterUserList(userList.current);
|
||||
setPageIndex(1);
|
||||
setPageIndex(0);
|
||||
setPageSize(10);
|
||||
|
||||
const startIndex = (index - 1) * size;
|
||||
const endIndex = index + size - 1;
|
||||
const newList = userList.current.slice(startIndex, endIndex);
|
||||
|
||||
console.log(userList.current);
|
||||
|
||||
setFilterUserList(newList);
|
||||
getUsers();
|
||||
}
|
||||
|
||||
function handleFilterUsers(input: string) {
|
||||
setInputValue(input);
|
||||
|
||||
if (input === "") {
|
||||
resetFilter();
|
||||
setFilterUserList(userList.current);
|
||||
} else {
|
||||
const filteredList = userList.current.filter(
|
||||
(user) =>
|
||||
user.user.toLowerCase().includes(input.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(input.toLowerCase())
|
||||
const filteredList = userList.current.filter((user:Users) =>
|
||||
user.username.toLowerCase().includes(input.toLowerCase())
|
||||
);
|
||||
setFilterUserList(filteredList);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeleteUser(index) {
|
||||
const user = _.cloneDeepWith(userList.current);
|
||||
user.splice(index, 1);
|
||||
userList.current = user;
|
||||
|
||||
const userFilter = _.cloneDeepWith(filterUserList);
|
||||
userFilter.splice(index, 1);
|
||||
setFilterUserList(userFilter);
|
||||
|
||||
resetFilter();
|
||||
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(index, user) {
|
||||
const newUser = _.cloneDeepWith(userList.current);
|
||||
newUser[index].password = user.password;
|
||||
newUser[index].user = user.username;
|
||||
userList.current = newUser;
|
||||
resetFilter();
|
||||
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 handleNewUser(user) {
|
||||
const newUser = {
|
||||
user: user.username,
|
||||
email: generateRandomString(50) + "@example.com",
|
||||
password: user.password,
|
||||
register_date: generateRandomDate(),
|
||||
};
|
||||
function handleDisableUser(check, userId, user) {
|
||||
const userEdit = cloneDeep(user);
|
||||
userEdit.is_active = !check;
|
||||
|
||||
userList.current.unshift(newUser);
|
||||
console.log(userList.current);
|
||||
updateUser(userId, userEdit)
|
||||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User edited!",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on edit user",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resetFilter();
|
||||
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"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="main-page-panel">
|
||||
<div className="m-auto flex h-full flex-row justify-center">
|
||||
<div className="basis-5/6">
|
||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
Welcome back!
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Here's a list of all users!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2"></div>
|
||||
</div>
|
||||
|
||||
{userList.current.length === 0 && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2>There's no users left :)</h2>
|
||||
<div className="flex flex-col">
|
||||
<Header />
|
||||
{userData && (
|
||||
<div className="main-page-panel">
|
||||
<div className="m-auto flex h-full flex-row justify-center">
|
||||
<div className="basis-5/6">
|
||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
Welcome back!
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Navigate through this section to efficiently oversee all
|
||||
application users. From here, you can seamlessly manage
|
||||
user accounts.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{userList.current.length > 0 && (
|
||||
<div className="flex items-center space-x-2"></div>
|
||||
</div>
|
||||
|
||||
{userList.current.length === 0 && !loadingUsers && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2>There's no users registered :)</h2>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-1 items-center space-x-2">
|
||||
|
|
@ -285,8 +223,8 @@ export default function AdminPage() {
|
|||
{inputValue.length > 0 && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
resetFilter();
|
||||
setInputValue("");
|
||||
setFilterUserList(userList.current);
|
||||
}}
|
||||
variant="ghost"
|
||||
className="h-8 px-2 lg:px-3"
|
||||
|
|
@ -311,90 +249,183 @@ export default function AdminPage() {
|
|||
</UserManagementModal>
|
||||
</div>
|
||||
</div>
|
||||
{loadingUsers && (
|
||||
<div>
|
||||
<strong>Loading...</strong>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted
|
||||
custom-scroll"
|
||||
className={
|
||||
"max-h-[26rem] min-h-[26rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
|
||||
(loadingUsers ? " border-0" : "")
|
||||
}
|
||||
>
|
||||
<Table className="table-fixed bg-muted outline-1 ">
|
||||
<TableHeader>
|
||||
<Table className={"table-fixed bg-muted outline-1"}>
|
||||
<TableHeader
|
||||
className={
|
||||
loadingUsers
|
||||
? "hidden"
|
||||
: "table-fixed bg-muted outline-1"
|
||||
}
|
||||
>
|
||||
<TableRow>
|
||||
<TableHead className="h-10">User</TableHead>
|
||||
<TableHead className="h-10">Password</TableHead>
|
||||
<TableHead className="h-10">Id</TableHead>
|
||||
<TableHead className="h-10">Username</TableHead>
|
||||
<TableHead className="h-10">Active</TableHead>
|
||||
<TableHead className="h-10">Superuser</TableHead>
|
||||
<TableHead className="h-10">Created At</TableHead>
|
||||
<TableHead className="h-10">Updated At</TableHead>
|
||||
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filterUserList.map((user, index) => (
|
||||
<TableRow key={user.user}>
|
||||
<TableCell className="truncate py-2 font-medium">
|
||||
{user.user}
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
{user.password}
|
||||
</TableCell>
|
||||
<TableCell className="flex w-[100px] py-2 text-right">
|
||||
<div className="flex">
|
||||
<UserManagementModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.user}`}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Edit"
|
||||
icon={"UserPlus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleEditUser(index, user);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Edit" side="top">
|
||||
<IconComponent
|
||||
name="Pencil"
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</UserManagementModal>
|
||||
|
||||
{!loadingUsers && (
|
||||
<TableBody>
|
||||
{filterUserList.map((user:UserInputType, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="truncate py-2 font-medium">
|
||||
<ShadTooltip content={user.id}>
|
||||
<span className="cursor-default">
|
||||
{user.id}
|
||||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
<ShadTooltip content={user.username}>
|
||||
<span className="cursor-default">
|
||||
{user.username}
|
||||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
|
||||
<ConfirmationModal
|
||||
title="Delete"
|
||||
titleHeader="Delete User"
|
||||
title="Edit"
|
||||
titleHeader={`${user.username}`}
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you sure you want to delete this user? This action cannot be undone."
|
||||
modalContent="Are you completely confident about the changes you are making to this user?"
|
||||
cancelText="Cancel"
|
||||
confirmationText="Delete"
|
||||
icon={"UserMinus2"}
|
||||
confirmationText="Confirm"
|
||||
icon={"UserCog2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleDeleteUser(index);
|
||||
handleDisableUser(
|
||||
user.is_active,
|
||||
user.id,
|
||||
user
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Delete" side="top">
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="ml-2 h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
<Checkbox
|
||||
id="is_active"
|
||||
checked={user.is_active}
|
||||
/>
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</TableCell>
|
||||
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
|
||||
<ConfirmationModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.username}`}
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you completely confident about the changes you are making to this user?"
|
||||
cancelText="Cancel"
|
||||
confirmationText="Confirm"
|
||||
icon={"UserCog2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleSuperUserEdit(
|
||||
user.is_superuser,
|
||||
user.id,
|
||||
user
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
id="is_superuser"
|
||||
checked={user.is_superuser}
|
||||
/>
|
||||
</ConfirmationModal>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2 ">
|
||||
{
|
||||
new Date(user.create_at!)
|
||||
.toISOString()
|
||||
.split("T")[0]
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
{
|
||||
new Date(user.updated_at!)
|
||||
.toISOString()
|
||||
.split("T")[0]
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell className="flex w-[100px] py-2 text-right">
|
||||
<div className="flex">
|
||||
<UserManagementModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.id}`}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Save"
|
||||
icon={"UserPlus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, editUser) => {
|
||||
handleEditUser(user.id, editUser);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Edit" side="top">
|
||||
<IconComponent
|
||||
name="Pencil"
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</UserManagementModal>
|
||||
|
||||
<ConfirmationModal
|
||||
title="Delete"
|
||||
titleHeader="Delete User"
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you sure you want to delete this user? This action cannot be undone."
|
||||
cancelText="Cancel"
|
||||
confirmationText="Delete"
|
||||
icon={"UserMinus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleDeleteUser(user);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Delete" side="top">
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="ml-2 h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<PaginatorComponent
|
||||
pageIndex={index}
|
||||
pageSize={size}
|
||||
totalRowsCount={filterUserList.length}
|
||||
totalRowsCount={totalRowsCount}
|
||||
paginate={(pageIndex, pageSize) => {
|
||||
handleChangePagination(pageSize, pageIndex);
|
||||
}}
|
||||
></PaginatorComponent>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
283
src/frontend/src/pages/ApiKeysPage/index.tsx
Normal file
283
src/frontend/src/pages/ApiKeysPage/index.tsx
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
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 { deleteApiKey, getApiKey } from "../../controllers/API";
|
||||
import ConfirmationModal from "../../modals/ConfirmationModal";
|
||||
import SecretKeyModal from "../../modals/SecretKeyModal";
|
||||
|
||||
import moment from "moment";
|
||||
import Header from "../../components/headerComponent";
|
||||
import {
|
||||
API_PAGE_PARAGRAPH_1,
|
||||
API_PAGE_PARAGRAPH_2,
|
||||
API_PAGE_USER_KEYS,
|
||||
LAST_USED_SPAN_1,
|
||||
LAST_USED_SPAN_2,
|
||||
} from "../../constants/constants";
|
||||
import { ApiKey } from "../../types/components";
|
||||
|
||||
export default function ApiKeysPage() {
|
||||
const [loadingKeys, setLoadingKeys] = useState(true);
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const { userData } = useContext(AuthContext);
|
||||
const [userId, setUserId] = useState("");
|
||||
const keysList = useRef([]);
|
||||
|
||||
useEffect(() => {
|
||||
getKeys();
|
||||
}, [userData]);
|
||||
|
||||
function getKeys() {
|
||||
setLoadingKeys(true);
|
||||
if (userData) {
|
||||
getApiKey()
|
||||
.then((keys: [ApiKey]) => {
|
||||
keysList.current = keys["api_keys"];
|
||||
setUserId(keys["user_id"]);
|
||||
setLoadingKeys(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoadingKeys(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilter() {
|
||||
getKeys();
|
||||
}
|
||||
|
||||
function handleDeleteKey(keys) {
|
||||
deleteApiKey(keys)
|
||||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! Key deleted!",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on delete key",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function lastUsedMessage() {
|
||||
return (
|
||||
<div className="text-xs">
|
||||
<span>
|
||||
{LAST_USED_SPAN_1}
|
||||
<br></br> {LAST_USED_SPAN_2}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header></Header>
|
||||
{userData && (
|
||||
<div className="main-page-panel">
|
||||
<div className="m-auto flex h-full flex-row justify-center">
|
||||
<div className="basis-5/6">
|
||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
API keys
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{API_PAGE_PARAGRAPH_1}
|
||||
<br />
|
||||
{API_PAGE_PARAGRAPH_2}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2"></div>
|
||||
</div>
|
||||
|
||||
{keysList.current &&
|
||||
keysList.current.length === 0 &&
|
||||
!loadingKeys && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2>{API_PAGE_USER_KEYS}</h2>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
{loadingKeys && (
|
||||
<div>
|
||||
<strong>Loading...</strong>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={
|
||||
"max-h-[15rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
|
||||
(loadingKeys ? " border-0" : "")
|
||||
}
|
||||
>
|
||||
{keysList.current &&
|
||||
keysList.current.length > 0 &&
|
||||
!loadingKeys && (
|
||||
<Table className={"table-fixed bg-muted outline-1"}>
|
||||
<TableHeader
|
||||
className={
|
||||
loadingKeys
|
||||
? "hidden"
|
||||
: "table-fixed bg-muted outline-1"
|
||||
}
|
||||
>
|
||||
<TableRow>
|
||||
<TableHead className="h-10">Name</TableHead>
|
||||
<TableHead className="h-10">Key</TableHead>
|
||||
<TableHead className="h-10">Created</TableHead>
|
||||
<TableHead className="flex h-10 items-center">
|
||||
Last Used
|
||||
<ShadTooltip
|
||||
side="top"
|
||||
content={lastUsedMessage()}
|
||||
>
|
||||
<div>
|
||||
<IconComponent
|
||||
name="Info"
|
||||
className="ml-1 h-3 w-3"
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</TableHead>
|
||||
<TableHead className="h-10">Total Uses</TableHead>
|
||||
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{!loadingKeys && (
|
||||
<TableBody>
|
||||
{keysList.current.map(
|
||||
(api_keys: ApiKey, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="truncate py-2">
|
||||
<ShadTooltip content={api_keys.name}>
|
||||
<span className="cursor-default">
|
||||
{api_keys.name ? api_keys.name : "-"}
|
||||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
<span className="cursor-default">
|
||||
{api_keys.api_key}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2 ">
|
||||
<ShadTooltip
|
||||
side="top"
|
||||
content={moment(
|
||||
api_keys.created_at
|
||||
).format("YYYY-MM-DD HH:mm")}
|
||||
>
|
||||
<div>
|
||||
{moment(api_keys.created_at).format(
|
||||
"YYYY-MM-DD HH:mm"
|
||||
)}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
<ShadTooltip
|
||||
side="top"
|
||||
content={
|
||||
moment(api_keys.last_used_at).format(
|
||||
"YYYY-MM-DD HH:mm"
|
||||
) === "Invalid date"
|
||||
? "Never"
|
||||
: moment(
|
||||
api_keys.last_used_at
|
||||
).format("YYYY-MM-DD HH:mm")
|
||||
}
|
||||
>
|
||||
<div>
|
||||
{moment(api_keys.last_used_at).format(
|
||||
"YYYY-MM-DD HH:mm"
|
||||
) === "Invalid date"
|
||||
? "Never"
|
||||
: moment(
|
||||
api_keys.last_used_at
|
||||
).format("YYYY-MM-DD HH:mm")}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
{api_keys.total_uses}
|
||||
</TableCell>
|
||||
<TableCell className="flex w-[100px] py-2 text-right">
|
||||
<div className="flex">
|
||||
<ConfirmationModal
|
||||
title="Delete"
|
||||
titleHeader="Delete User"
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you sure you want to delete this key? This action cannot be undone."
|
||||
cancelText="Cancel"
|
||||
confirmationText="Delete"
|
||||
icon={"UserMinus2"}
|
||||
data={api_keys.id}
|
||||
index={index}
|
||||
onConfirm={(index, keys) => {
|
||||
handleDeleteKey(keys);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip
|
||||
content="Delete"
|
||||
side="top"
|
||||
>
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="ml-2 h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<SecretKeyModal
|
||||
title="Create new secret key"
|
||||
cancelText="Cancel"
|
||||
confirmationText="Create secret key"
|
||||
icon={"Key"}
|
||||
data={userId}
|
||||
onCloseModal={getKeys}
|
||||
>
|
||||
<Button>
|
||||
<IconComponent name="Plus" className="mr-1 h-5 w-5" />
|
||||
Create new secret key
|
||||
</Button>
|
||||
</SecretKeyModal>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -431,6 +431,7 @@ export default function Page({
|
|||
zoomOnScroll={!view}
|
||||
zoomOnPinch={!view}
|
||||
panOnDrag={!view}
|
||||
proOptions={{hideAttribution: true}}
|
||||
>
|
||||
<Background className="" />
|
||||
{!view && (
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
const { data, templates } = useContext(typesContext);
|
||||
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
|
||||
useContext(TabsContext);
|
||||
const { setSuccessData, setErrorData } = useContext(alertContext);
|
||||
const { setSuccessData, setErrorData, setIsTweakPage } =
|
||||
useContext(alertContext);
|
||||
const [dataFilter, setFilterData] = useState(data);
|
||||
const [search, setSearch] = useState("");
|
||||
const isPending = tabsState[tabId]?.isPending;
|
||||
|
|
@ -100,7 +101,10 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
<div className="side-bar-button">
|
||||
{flow && flow.data && (
|
||||
<ApiModal flow={flow} disable={!isBuilt}>
|
||||
<div className={classNames("extra-side-bar-buttons")}>
|
||||
<div
|
||||
className={classNames("extra-side-bar-buttons")}
|
||||
onClick={() => setIsTweakPage(true)}
|
||||
>
|
||||
<IconComponent
|
||||
name="Code2"
|
||||
className={
|
||||
|
|
@ -121,7 +125,6 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
}
|
||||
onClick={(event) => {
|
||||
saveFlow(flow!);
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default function ViewPage() {
|
|||
|
||||
// Set flow tab id
|
||||
useEffect(() => {
|
||||
setTabId(id);
|
||||
setTabId(id!);
|
||||
}, [id]);
|
||||
|
||||
// Initialize state variable for the version
|
||||
|
|
@ -26,7 +26,7 @@ export default function ViewPage() {
|
|||
{flows.length > 0 &&
|
||||
tabId !== "" &&
|
||||
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
|
||||
<Page view flow={flows.find((flow) => flow.id === tabId)} />
|
||||
<Page view flow={flows.find((flow) => flow.id === tabId)!} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useContext, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import { CONTROL_LOGIN_STATE } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { getLoggedUser, onLogin } from "../../controllers/API";
|
||||
import { LoginType } from "../../types/api";
|
||||
import {
|
||||
inputHandlerEventType,
|
||||
loginInputStateType,
|
||||
|
|
@ -15,12 +19,49 @@ export default function LoginPage(): JSX.Element {
|
|||
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
|
||||
|
||||
const { password, username } = inputState;
|
||||
const { login, getAuthentication, setUserData, setIsAdmin } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
function signIn() {
|
||||
const user: LoginType = {
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
onLogin(user)
|
||||
.then((user) => {
|
||||
login(user.access_token, user.refresh_token);
|
||||
getUser();
|
||||
navigate("/");
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error signing in",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getUser() {
|
||||
if (getAuthentication()) {
|
||||
setTimeout(() => {
|
||||
getLoggedUser()
|
||||
.then((user) => {
|
||||
const isSuperUser = user.is_superuser;
|
||||
setIsAdmin(isSuperUser);
|
||||
setUserData(user);
|
||||
})
|
||||
.catch((error) => {});
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
|
|
@ -28,7 +69,7 @@ export default function LoginPage(): JSX.Element {
|
|||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
signIn();
|
||||
const data = Object.fromEntries(new FormData(event.currentTarget));
|
||||
event.preventDefault();
|
||||
}}
|
||||
|
|
@ -92,7 +133,7 @@ export default function LoginPage(): JSX.Element {
|
|||
</Form.Submit>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Link to="">
|
||||
<Link to="/signup">
|
||||
<Button className="w-full" variant="outline">
|
||||
Don't have an account? <b>Sign Up</b>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { FormEvent, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FormEvent, useContext, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import { CONTROL_INPUT_STATE } from "../../constants/constants";
|
||||
import {
|
||||
CONTROL_INPUT_STATE,
|
||||
SIGN_UP_SUCCESS,
|
||||
} from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { addUser } from "../../controllers/API";
|
||||
import {
|
||||
UserInputType,
|
||||
inputHandlerEventType,
|
||||
signUpInputStateType,
|
||||
} from "../../types/components";
|
||||
|
|
@ -15,12 +21,42 @@ export default function SignUp(): JSX.Element {
|
|||
useState<signUpInputStateType>(CONTROL_INPUT_STATE);
|
||||
|
||||
const { password, cnfPassword, username } = inputState;
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
function handleSignup(): void {
|
||||
const { username, password } = inputState;
|
||||
const newUser: UserInputType = {
|
||||
username,
|
||||
password,
|
||||
};
|
||||
addUser(newUser)
|
||||
.then((user) => {
|
||||
setSuccessData({
|
||||
title: SIGN_UP_SUCCESS,
|
||||
});
|
||||
navigate("/login");
|
||||
})
|
||||
.catch((error) => {
|
||||
const {
|
||||
response: {
|
||||
data: { detail },
|
||||
},
|
||||
} = error;
|
||||
setErrorData({
|
||||
title: "Error signing up",
|
||||
list: [detail],
|
||||
});
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Root
|
||||
onSubmit={(event: FormEvent<HTMLFormElement>) => {
|
||||
|
|
@ -120,7 +156,14 @@ export default function SignUp(): JSX.Element {
|
|||
</div>
|
||||
<div className="w-full">
|
||||
<Form.Submit asChild>
|
||||
<Button className="mr-3 mt-6 w-full">Sign up</Button>
|
||||
<Button
|
||||
className="mr-3 mt-6 w-full"
|
||||
onClick={() => {
|
||||
handleSignup();
|
||||
}}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
|
|
|
|||
|
|
@ -1,32 +1,116 @@
|
|||
import { Route, Routes } from "react-router-dom";
|
||||
import { ProtectedAdminRoute } from "./components/authAdminGuard";
|
||||
import { ProtectedRoute } from "./components/authGuard";
|
||||
import { ProtectedLoginRoute } from "./components/authLoginGuard";
|
||||
import { CatchAllRoute } from "./components/catchAllRoutes";
|
||||
import AdminPage from "./pages/AdminPage";
|
||||
import LoginAdminPage from "./pages/AdminPage/LoginPage";
|
||||
import ApiKeysPage from "./pages/ApiKeysPage";
|
||||
import CommunityPage from "./pages/CommunityPage";
|
||||
import FlowPage from "./pages/FlowPage";
|
||||
import HomePage from "./pages/MainPage";
|
||||
import ViewPage from "./pages/ViewPage";
|
||||
import DeleteAccountPage from "./pages/deleteAccountPage";
|
||||
import LoginPage from "./pages/loginPage";
|
||||
import SignUp from "./pages/signUpPage";
|
||||
|
||||
const Router = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/community" element={<CommunityPage />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<HomePage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/community"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<CommunityPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/flow/:id/">
|
||||
<Route path="" element={<FlowPage />} />
|
||||
<Route path="view" element={<ViewPage />} />
|
||||
<Route
|
||||
path=""
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<FlowPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="view"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ViewPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="*" element={<HomePage />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<CatchAllRoute />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
{/* <Route path="/signup" element={<SignUp />} /> */}
|
||||
<Route path="/login/admin" element={<LoginAdminPage />} />
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<LoginPage />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/signup"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<SignUp />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/login/admin"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<LoginAdminPage />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/admin" element={<AdminPage />} />
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<ProtectedAdminRoute>
|
||||
<AdminPage />
|
||||
</ProtectedAdminRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/account">
|
||||
<Route path="delete" element={<DeleteAccountPage />}></Route>
|
||||
<Route
|
||||
path="delete"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<DeleteAccountPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
></Route>
|
||||
<Route
|
||||
path="api-keys"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ApiKeysPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
></Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -488,10 +488,10 @@
|
|||
@apply flex-max-width h-12 items-center justify-between border-border bg-muted;
|
||||
}
|
||||
.header-start-display {
|
||||
@apply flex w-96 items-center justify-start gap-2;
|
||||
@apply flex w-[30%] items-center justify-start gap-2;
|
||||
}
|
||||
.header-end-division {
|
||||
@apply flex w-96 justify-end px-2;
|
||||
@apply flex w-[30%] justify-end px-2;
|
||||
}
|
||||
.header-end-display {
|
||||
@apply ml-auto mr-2 flex items-center gap-5;
|
||||
|
|
|
|||
|
|
@ -62,3 +62,27 @@ export type UploadFileTypeAPI = {
|
|||
file_path: string;
|
||||
flowId: string;
|
||||
};
|
||||
|
||||
export type LoginType = {
|
||||
grant_type?: string;
|
||||
username: string;
|
||||
password: string;
|
||||
scrope?: string;
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
};
|
||||
|
||||
export type LoginAuthType = {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type?: string;
|
||||
};
|
||||
|
||||
export type Users = {
|
||||
id: string;
|
||||
username: string;
|
||||
is_active: boolean;
|
||||
is_superuser: boolean;
|
||||
create_at: Date;
|
||||
updated_at: Date;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ export type signUpInputStateType = {
|
|||
|
||||
export type inputHandlerEventType = {
|
||||
target: {
|
||||
value: string;
|
||||
value: string | boolean;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
|
@ -261,6 +261,29 @@ export type loginInputStateType = {
|
|||
password: string;
|
||||
};
|
||||
|
||||
export type UserInputType = {
|
||||
username: string;
|
||||
password: string;
|
||||
is_active?: boolean;
|
||||
is_superuser?: boolean;
|
||||
id?: string;
|
||||
create_at?: string;
|
||||
updated_at?:string;
|
||||
};
|
||||
|
||||
export type ApiKeyType = {
|
||||
title: string;
|
||||
cancelText: string;
|
||||
confirmationText: string;
|
||||
children: ReactElement;
|
||||
icon: string;
|
||||
data?: any;
|
||||
onCloseModal: () => void;
|
||||
};
|
||||
|
||||
export type ApiKeyInputType = {
|
||||
apikeyname: string;
|
||||
};
|
||||
export type groupedObjType = {
|
||||
family: string;
|
||||
type: string;
|
||||
|
|
@ -508,3 +531,16 @@ export type validationStatusType = {
|
|||
progress: number;
|
||||
valid: boolean;
|
||||
};
|
||||
|
||||
export type ApiKey = {
|
||||
id: string;
|
||||
api_key: string;
|
||||
name: string;
|
||||
created_at: string;
|
||||
last_used_at: string;
|
||||
total_uses: number;
|
||||
};
|
||||
export type fetchErrorComponentType = {
|
||||
message: string;
|
||||
description: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import { Users } from "../api";
|
||||
|
||||
export type AuthContextType = {
|
||||
isAdmin: boolean;
|
||||
setIsAdmin: (isAdmin: boolean) => void;
|
||||
isAuthenticated: boolean;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
login: (accessToken: string, refreshToken: string) => void;
|
||||
logout: () => void;
|
||||
refreshAccessToken: (refreshToken: string) => Promise<void>;
|
||||
userData: userData | null;
|
||||
setUserData: (userData: userData | null) => void;
|
||||
};
|
||||
|
||||
export type userData = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
userData: Users | null;
|
||||
setUserData: (userData: Users | null) => void;
|
||||
getAuthentication: () => boolean;
|
||||
authenticationErrorCount: number;
|
||||
autoLogin: boolean;
|
||||
setAutoLogin: (autoLogin: boolean) => void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ export type typesContextType = {
|
|||
setTemplates: (newState: {}) => void;
|
||||
data: APIDataType;
|
||||
setData: (newState: {}) => void;
|
||||
fetchError: boolean;
|
||||
setFetchError: (newState: boolean) => void;
|
||||
};
|
||||
|
||||
export type alertContextType = {
|
||||
|
|
@ -39,11 +41,15 @@ export type alertContextType = {
|
|||
removeFromNotificationList: (index: string) => void;
|
||||
loading: boolean;
|
||||
setLoading: (newState: boolean) => void;
|
||||
isTweakPage: boolean;
|
||||
setIsTweakPage: (newState: boolean) => void;
|
||||
};
|
||||
|
||||
export type darkContextType = {
|
||||
dark: {};
|
||||
setDark: (newState: {}) => void;
|
||||
stars: number;
|
||||
setStars: (stars: number) => void;
|
||||
};
|
||||
|
||||
export type locationContextType = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Edge } from "reactflow";
|
||||
import { Edge, Node } from "reactflow";
|
||||
import { NodeType } from "../flow";
|
||||
|
||||
export type cleanEdgesType = {
|
||||
|
|
@ -9,6 +9,11 @@ export type cleanEdgesType = {
|
|||
updateEdge: (edge: Edge[]) => void;
|
||||
};
|
||||
|
||||
export type unselectAllNodesType = {
|
||||
updateNodes: (nodes: Node[]) => void;
|
||||
data: Node[] | null;
|
||||
};
|
||||
|
||||
export type updateEdgesHandleIdsType = {
|
||||
nodes: NodeType[];
|
||||
edges: Edge[];
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import _ from "lodash";
|
|||
import {
|
||||
Connection,
|
||||
Edge,
|
||||
Node,
|
||||
ReactFlowInstance,
|
||||
ReactFlowJsonObject,
|
||||
} from "reactflow";
|
||||
|
|
@ -16,6 +17,7 @@ import {
|
|||
import {
|
||||
cleanEdgesType,
|
||||
updateEdgesHandleIdsType,
|
||||
unselectAllNodesType,
|
||||
} from "../types/utils/reactflowUtils";
|
||||
import { toNormalCase } from "./utils";
|
||||
|
||||
|
|
@ -64,7 +66,14 @@ export function cleanEdges({
|
|||
updateEdge(newEdges);
|
||||
}
|
||||
|
||||
// add comments to this function
|
||||
export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
|
||||
let newNodes = _.cloneDeep(data);
|
||||
newNodes!.forEach((node: Node) => {
|
||||
node.selected = false;
|
||||
});
|
||||
updateNodes(newNodes!);
|
||||
}
|
||||
|
||||
export function isValidConnection(
|
||||
{ source, target, sourceHandle, targetHandle }: Connection,
|
||||
reactFlowInstance: ReactFlowInstance
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import {
|
|||
Edit,
|
||||
Eraser,
|
||||
ExternalLink,
|
||||
Eye,
|
||||
EyeOff,
|
||||
File,
|
||||
FileDown,
|
||||
FileSearch,
|
||||
|
|
@ -33,6 +35,7 @@ import {
|
|||
HelpCircle,
|
||||
Home,
|
||||
Info,
|
||||
Key,
|
||||
Laptop2,
|
||||
Layers,
|
||||
Lightbulb,
|
||||
|
|
@ -59,7 +62,9 @@ import {
|
|||
TerminalSquare,
|
||||
Trash2,
|
||||
Undo,
|
||||
Unplug,
|
||||
Upload,
|
||||
UserCog2,
|
||||
UserMinus2,
|
||||
UserPlus2,
|
||||
Users2,
|
||||
|
|
@ -290,4 +295,9 @@ export const nodeIconsLucide: iconsType = {
|
|||
ChevronsLeft,
|
||||
FaGithub,
|
||||
FaApple,
|
||||
EyeOff,
|
||||
Eye,
|
||||
UserCog2,
|
||||
Key,
|
||||
Unplug,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue