merge fix

This commit is contained in:
Cristhian Zanforlin Lousa 2023-08-31 10:37:46 -03:00
commit 7d1a6c310d
121 changed files with 5141 additions and 2257 deletions

File diff suppressed because it is too large Load diff

View file

@ -3,11 +3,11 @@
"version": "0.1.2",
"private": true,
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@headlessui/react": "^1.7.10",
"@heroicons/react": "^2.0.15",
"@mui/material": "^5.11.9",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"@mui/material": "^5.14.7",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
@ -24,44 +24,45 @@
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.6",
"@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3",
"@tabler/icons-react": "^2.32.0",
"@tailwindcss/forms": "^0.5.6",
"@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0",
"accordion": "^3.0.2",
"ace-builds": "^1.16.0",
"ace-builds": "^1.24.1",
"add": "^2.0.6",
"ansi-to-html": "^0.7.2",
"axios": "^1.3.2",
"axios": "^1.5.0",
"base64-js": "^1.5.1",
"class-variance-authority": "^0.6.0",
"class-variance-authority": "^0.6.1",
"clsx": "^1.2.1",
"dompurify": "^3.0.4",
"esbuild": "^0.17.18",
"dompurify": "^3.0.5",
"esbuild": "^0.17.19",
"lodash": "^4.17.21",
"lucide-react": "^0.233.0",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-cookie": "^4.1.1",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.2",
"react-icons": "^4.8.0",
"react-error-boundary": "^4.0.11",
"react-icons": "^4.10.1",
"react-laag": "^2.0.5",
"react-markdown": "^8.0.7",
"react-router-dom": "^6.8.1",
"react-router-dom": "^6.15.0",
"react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.0",
"react-tooltip": "^5.13.1",
"reactflow": "^11.5.5",
"rehype-mathjax": "^4.0.2",
"react-tabs": "^6.0.2",
"react-tooltip": "^5.21.1",
"reactflow": "^11.8.3",
"rehype-mathjax": "^4.0.3",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"shadcn-ui": "^0.2.2",
"shadcn-ui": "^0.2.3",
"short-unique-id": "^4.4.4",
"switch": "^0.0.0",
"table": "^6.8.1",
"tailwind-merge": "^1.13.0",
"tailwindcss-animate": "^1.0.5",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uuid": "^9.0.0",
"vite-plugin-svgr": "^3.2.0",
"web-vitals": "^2.1.4"
@ -95,26 +96,26 @@
"proxy": "http://127.0.0.1:7860",
"devDependencies": {
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.62",
"@swc/core": "^1.3.80",
"@tailwindcss/typography": "^0.5.9",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/lodash": "^4.14.194",
"@types/node": "^16.18.12",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/uuid": "^9.0.1",
"@vitejs/plugin-react-swc": "^3.0.0",
"autoprefixer": "^10.4.14",
"daisyui": "^3.1.1",
"postcss": "^8.4.23",
"@types/lodash": "^4.14.197",
"@types/node": "^16.18.46",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@types/uuid": "^9.0.2",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.15",
"daisyui": "^3.6.3",
"postcss": "^8.4.29",
"prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.2",
"prettier-plugin-organize-imports": "^3.2.3",
"prettier-plugin-tailwindcss": "^0.3.0",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.2",
"vite": "^4.3.9"
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2",
"vite": "^4.4.9"
}
}

View file

@ -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";
@ -42,8 +42,11 @@ 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

View file

@ -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>

View 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;
};

View 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;
};

View 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;
};

View 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;
};

View file

@ -49,7 +49,6 @@ export default function CodeTabsComponent({
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext);
const { isTweakPage, setIsTweakPage } = useContext(alertContext);
useEffect(() => {
if (flow && flow["data"]!["nodes"]) {
@ -58,16 +57,14 @@ export default function CodeTabsComponent({
}, [flow]);
useEffect(() => {
unselectAllNodes({
data,
updateNodes: (nodes) => {
reactFlowInstance?.setNodes(nodes);
},
});
return () => {
if (isTweakPage) setIsTweakPage(false);
};
if(tweaks){
unselectAllNodes({
data,
updateNodes: (nodes) => {
reactFlowInstance?.setNodes(nodes);
},
});
}
}, []);
const copyToClipboard = () => {

View file

@ -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
className={className}
style={{ color: iconColor }}
strokeWidth={1.5}
/>
);
}
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;

View file

@ -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>

View file

@ -81,7 +81,8 @@ export default function InputComponent({
? "input-component-true-button"
: "input-component-false-button"
)}
onClick={() => {
onClick={(event) => {
event.preventDefault();
setPwdVisible(!pwdVisible);
}}
>

View file

@ -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) {
@ -605,3 +618,21 @@ 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.";

View file

@ -25,9 +25,7 @@ const initialValue: alertContextType = {
notificationList: [],
pushNotificationList: () => {},
clearNotificationList: () => {},
removeFromNotificationList: () => {},
isTweakPage: false,
setIsTweakPage: () => {},
removeFromNotificationList: () => {}
};
export const alertContext = createContext<alertContextType>(initialValue);
@ -123,8 +121,6 @@ export function AlertProvider({ children }: { children: ReactNode }) {
return (
<alertContext.Provider
value={{
isTweakPage,
setIsTweakPage,
removeFromNotificationList,
clearNotificationList,
notificationList,

View file

@ -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}

View file

@ -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,
}}

View file

@ -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>
</>
);
}

View file

@ -1,3 +1,4 @@
import { AxiosError } from "axios";
import _ from "lodash";
import {
ReactNode,
@ -21,7 +22,7 @@ import {
import { APIClassType, APITemplateType } from "../types/api";
import { tweakType } from "../types/components";
import { FlowType, NodeDataType, NodeType } from "../types/flow";
import { TabsContextType, TabsState, errorsVarType } from "../types/tabs";
import { TabsContextType, TabsState } from "../types/tabs";
import {
addVersionToDuplicates,
updateIds,
@ -29,6 +30,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 });
@ -68,7 +70,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("");
@ -117,24 +121,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 {
@ -143,9 +149,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
processFlowEdges(flow);
processFlowNodes(flow);
} catch (e) {
console.error(e);
}
} catch (e) {}
});
}
@ -478,7 +482,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 {
@ -579,6 +582,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);
@ -601,7 +605,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
} catch (err) {
setErrorData(err as errorsVarType);
setErrorData({
title: "Error while saving changes",
list: [(err as AxiosError).message],
});
}
}

View file

@ -10,6 +10,7 @@ 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
@ -37,55 +38,56 @@ export function TypesProvider({ children }: { children: ReactNode }) {
const [data, setData] = useState({});
const [fetchError, setFetchError] = useState(false);
const { setLoading } = useContext(alertContext);
const { getAuthentication } = useContext(AuthContext);
useEffect(() => {
// 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 && result?.status === 200) {
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;
}, {})
);
}
} catch (error) {
console.error("An error has occurred while fetching types.");
await getHealth().catch((e) => {
setFetchError(true);
});
);
}
} catch (error) {
console.error("An error has occurred while fetching types.");
await getHealth().catch((e) => {
setFetchError(true);
});
}
getTypes();
}, []);
}
function deleteNode(idx: string) {
reactFlowInstance!.setNodes(

View file

@ -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 };

View file

@ -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;
}
@ -305,8 +312,8 @@ export async function getHealth() {
*/
export async function getBuildStatus(
flowId: string
): Promise<BuildStatusTypeAPI> {
return await api.get(`/api/v1/build/${flowId}/status`);
): Promise<AxiosResponse<BuildStatusTypeAPI>> {
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;
}
}

View file

@ -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();

View 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>
);
}

View file

@ -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>

View file

@ -40,7 +40,6 @@ export default function CodeAreaModal({
if (dynamic && Object.keys(nodeClass!.template).length > 2) {
return;
}
if (!isTweakPage) processCode();
}, []);
function processNonDynamicField() {

View file

@ -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 (

View file

@ -8,7 +8,7 @@ import { classNames } from "../../utils/utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
import _ from "lodash";
import _, { set } from "lodash";
import AccordionComponent from "../../components/AccordionComponent";
import IconComponent from "../../components/genericIconComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
@ -23,9 +23,11 @@ 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";
import { getBuildStatus } from "../../controllers/API";
export default function FormModal({
flow,
@ -60,6 +62,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);
@ -153,14 +156,26 @@ export default function FormModal({
function handleOnClose(event: CloseEvent): void {
if (isOpen.current) {
getBuildStatus(flow.id).then((response) => {
if (response.data.built) {
connectWS();
}
else {
setErrorData({
title: "Please build the flow again before using the chat."
})
}
}).catch((error) => {
setErrorData({title:error.data?.detail?error.data.detail:error.message})
});
setErrorData({ title: event.reason });
setTimeout(() => {
connectWS();
setLockChat(false);
}, 1000);
}
}
//TODO improve check of user authentication
function getWebSocketUrl(
chatId: string,
isDevelopment: boolean = false
@ -171,9 +186,8 @@ export default function FormModal({
const host = isDevelopment ? "localhost:7860" : window.location.host;
const chatEndpoint = `/api/v1/chat/${chatId}`;
return `${
isDevelopment ? "ws" : webSocketProtocol
}://${host}${chatEndpoint}`;
return `${isDevelopment ? "ws" : webSocketProtocol
}://${host}${chatEndpoint}?token=${encodeURIComponent(accessToken!)}`;
}
function handleWsMessage(data: any) {
@ -195,20 +209,20 @@ export default function FormModal({
newChatHistory.push(
chatItem.files
? {
isSend: !chatItem.is_bot,
message: chatItem.message,
template: chatItem.template,
thought: chatItem.intermediate_steps,
files: chatItem.files,
chatKey: chatItem.chatKey,
}
isSend: !chatItem.is_bot,
message: chatItem.message,
template: chatItem.template,
thought: chatItem.intermediate_steps,
files: chatItem.files,
chatKey: chatItem.chatKey,
}
: {
isSend: !chatItem.is_bot,
message: chatItem.message,
template: chatItem.template,
thought: chatItem.intermediate_steps,
chatKey: chatItem.chatKey,
}
isSend: !chatItem.is_bot,
message: chatItem.message,
template: chatItem.template,
thought: chatItem.intermediate_steps,
chatKey: chatItem.chatKey,
}
);
}
}
@ -258,7 +272,6 @@ export default function FormModal({
};
newWs.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received data:", data);
handleWsMessage(data);
//get chat history
};
@ -266,7 +279,6 @@ export default function FormModal({
handleOnClose(event);
};
newWs.onerror = (ev) => {
console.log(ev, "error");
if (flow.id === "") {
connectWS();
} else {
@ -292,7 +304,6 @@ export default function FormModal({
useEffect(() => {
connectWS();
return () => {
console.log("unmount");
console.log(ws);
if (ws.current) {
ws.current.close();
@ -431,73 +442,73 @@ export default function FormModal({
{tabsState[id.current]?.formKeysData?.input_keys
? Object.keys(
tabsState[id.current].formKeysData.input_keys!
).map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
tabsState[id.current].formKeysData.input_keys!
).map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={(value) =>
handleOnCheckedChange(value, key)
}
size="small"
disabled={tabsState[
id.current
].formKeysData.handle_keys!.some(
(t) => t === key
)}
/>
</div>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={(value) =>
handleOnCheckedChange(value, key)
}
size="small"
disabled={tabsState[
id.current
].formKeysData.handle_keys!.some(
(t) => t === key
)}
/>
</div>
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
{tabsState[id.current].formKeysData.handle_keys!.some(
(t) => t === key
) && (
</div>
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
{tabsState[id.current].formKeysData.handle_keys!.some(
(t) => t === key
) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea
className="custom-scroll"
value={
tabsState[id.current].formKeysData.input_keys![
key
]
}
onChange={(e) => {
//@ts-ignore
setTabsState((old: TabsState) => {
let newTabsState = _.cloneDeep(old);
newTabsState[
id.current
].formKeysData.input_keys![key] =
e.target.value;
return newTabsState;
});
}}
disabled={chatKey === key}
placeholder="Enter text..."
></Textarea>
</div>
</AccordionComponent>
</div>
))
<Textarea
className="custom-scroll"
value={
tabsState[id.current].formKeysData.input_keys![
key
]
}
onChange={(e) => {
//@ts-ignore
setTabsState((old: TabsState) => {
let newTabsState = _.cloneDeep(old);
newTabsState[
id.current
].formKeysData.input_keys![key] =
e.target.value;
return newTabsState;
});
}}
disabled={chatKey === key}
placeholder="Enter text..."
></Textarea>
</div>
</AccordionComponent>
</div>
))
: null}
{tabsState[id.current].formKeysData.memory_keys!.map(
(key, index) => (
@ -511,7 +522,7 @@ export default function FormModal({
<div className="-mb-1">
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={() => {}}
setEnabled={() => { }}
size="small"
disabled={true}
/>

View file

@ -121,6 +121,7 @@ export default function GenericModal({
}
function validatePrompt(closeModal: boolean): void {
//nodeClass is always null on tweaks
postValidatePrompt(field_name, inputValue, nodeClass!)
.then((apiReturn) => {
if (apiReturn.data) {
@ -135,7 +136,7 @@ export default function GenericModal({
setSuccessData({
title: "Prompt is ready",
});
setNodeClass!(apiReturn.data?.frontend_node);
if(JSON.stringify(apiReturn.data?.frontend_node)!==JSON.stringify({})) setNodeClass!(apiReturn.data?.frontend_node);
setModalOpen(closeModal);
setValue(inputValue);
}
@ -147,7 +148,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",

View file

@ -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"

View file

@ -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&apos;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>
</>
);

View 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>
)}
</>
);
}

View file

@ -421,6 +421,7 @@ export default function Page({
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}
proOptions={{hideAttribution: true}}
>
<Background className="" />
{!view && (

View file

@ -21,7 +21,7 @@ export default function ExtraSidebar(): JSX.Element {
const { data, templates } = useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
useContext(TabsContext);
const { setSuccessData, setErrorData, setIsTweakPage } =
const { setSuccessData, setErrorData } =
useContext(alertContext);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
@ -103,7 +103,6 @@ export default function ExtraSidebar(): JSX.Element {
<ApiModal flow={flow} disable={!isBuilt}>
<div
className={classNames("extra-side-bar-buttons")}
onClick={() => setIsTweakPage(true)}
>
<IconComponent
name="Code2"
@ -125,7 +124,6 @@ export default function ExtraSidebar(): JSX.Element {
}
onClick={(event) => {
saveFlow(flow!);
setSuccessData({ title: "Changes saved successfully" });
}}
>
<IconComponent

View file

@ -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>
);

View file

@ -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?&nbsp;<b>Sign Up</b>
</Button>

View file

@ -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">

View file

@ -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>
);

View file

@ -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;

View file

@ -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;
};

View file

@ -226,7 +226,7 @@ export type signUpInputStateType = {
export type inputHandlerEventType = {
target: {
value: string;
value: string | boolean;
name: string;
};
};
@ -269,6 +269,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;
@ -516,6 +539,15 @@ 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;

View file

@ -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;
};

View file

@ -41,13 +41,13 @@ 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 = {

View file

@ -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,
@ -61,6 +64,7 @@ import {
Undo,
Unplug,
Upload,
UserCog2,
UserMinus2,
UserPlus2,
Users2,
@ -291,5 +295,9 @@ export const nodeIconsLucide: iconsType = {
ChevronsLeft,
FaGithub,
FaApple,
EyeOff,
Eye,
UserCog2,
Key,
Unplug,
};

View file

@ -201,6 +201,12 @@ module.exports = {
".dark .theme-attribution .react-flow__attribution a": {
color: "black",
},
".text-align-last-left": {
"text-align-last": "left",
},
".text-align-last-right": {
"text-align-last": "right",
},
});
}),
require("@tailwindcss/typography"),