Merge remote-tracking branch 'origin/dev' into celery

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-09-01 15:24:07 -03:00
commit 5c258d08f0
174 changed files with 14281 additions and 4261 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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Before After
Before After

0
src/frontend/set_proxy.sh Executable file → Normal file
View file

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";
@ -9,10 +9,16 @@ import ErrorAlert from "./alerts/error";
import NoticeAlert from "./alerts/notice";
import SuccessAlert from "./alerts/success";
import CrashErrorComponent from "./components/CrashErrorComponent";
import FetchErrorComponent from "./components/fetchErrorComponent";
import LoadingComponent from "./components/loadingComponent";
import {
FETCH_ERROR_DESCRIPION,
FETCH_ERROR_MESSAGE,
} from "./constants/constants";
import { alertContext } from "./contexts/alertContext";
import { locationContext } from "./contexts/locationContext";
import { TabsContext } from "./contexts/tabsContext";
import { typesContext } from "./contexts/typesContext";
import Router from "./routes";
export default function App() {
@ -36,8 +42,12 @@ export default function App() {
successData,
successOpen,
setSuccessOpen,
setErrorData,
loading,
setLoading,
} = useContext(alertContext);
const navigate = useNavigate();
const { fetchError } = useContext(typesContext);
// Initialize state variable for the list of alerts
const [alertsList, setAlertsList] = useState<
@ -137,7 +147,14 @@ export default function App() {
>
{loading ? (
<div className="loading-page-panel">
<LoadingComponent remSize={50} />
{fetchError ? (
<FetchErrorComponent
description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent>
) : (
<LoadingComponent remSize={50} />
)}
</div>
) : (
<>

View file

@ -32,13 +32,6 @@ export default function AccordionComponent({
value === "" ? setValue(keyValue!) : setValue("");
}
const handleKeyDown = (event) => {
if (event.key === "Backspace") {
event.preventDefault();
event.stopPropagation();
}
};
return (
<>
<Accordion
@ -46,7 +39,6 @@ export default function AccordionComponent({
className="w-full"
value={value}
onValueChange={setValue}
onKeyDown={handleKeyDown}
>
<AccordionItem value={keyValue!} className="border-b">
<AccordionTrigger

View file

@ -0,0 +1,67 @@
import { useState } from "react";
import { dropdownButtonPropsType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
export default function DropdownButton({
firstButtonName,
onFirstBtnClick,
options,
}: dropdownButtonPropsType): JSX.Element {
const [showOptions, setShowOptions] = useState<boolean>(false);
return (
<div>
<DropdownMenu open={showOptions}>
<DropdownMenuTrigger asChild>
<Button
variant="primary"
className="relative pr-10"
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
onFirstBtnClick();
}}
>
{firstButtonName}
<div
className="absolute right-2 items-center text-muted-foreground"
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
setShowOptions(!showOptions);
}}
>
{!showOptions ? (
<IconComponent
name="ChevronDown"
/>
) : (
<IconComponent name="ChevronUp" />
)}
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
onPointerDownOutside={(event) => {
event.stopPropagation();
event.preventDefault();
setShowOptions(!showOptions);
}}
>
{options.map(({ name, onBtnClick }, index) => (
<DropdownMenuItem onClick={onBtnClick} key={index}>
{name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}

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

@ -60,6 +60,11 @@ export default function BuildTrigger({
],
});
}
if (errors.length === 0 && allNodesValid) {
setSuccessData({
title: "Flow is ready to run",
});
}
} catch (error) {
console.error("Error:", error);
} finally {

View file

@ -29,7 +29,9 @@ import {
TabsTrigger,
} from "../../components/ui/tabs";
import { darkContext } from "../../contexts/darkContext";
import { typesContext } from "../../contexts/typesContext";
import { codeTabsPropsType } from "../../types/components";
import { unselectAllNodes } from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
@ -45,6 +47,7 @@ export default function CodeTabsComponent({
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext);
useEffect(() => {
if (flow && flow["data"]!["nodes"]) {
@ -52,6 +55,17 @@ export default function CodeTabsComponent({
}
}, [flow]);
useEffect(() => {
if (tweaks) {
unselectAllNodes({
data,
updateNodes: (nodes) => {
reactFlowInstance?.setNodes(nodes);
},
});
}
}, []);
const copyToClipboard = () => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
@ -159,13 +173,13 @@ export default function CodeTabsComponent({
)}
</div>
{tabs.map((tab, index) => (
{tabs.map((tab, idx) => (
<TabsContent
value={index.toString()}
value={idx.toString()}
className="api-modal-tabs-content"
key={index} // Remember to add a unique key prop
key={idx} // Remember to add a unique key prop
>
{index < 4 ? (
{idx < 4 ? (
<>
{tab.description && (
<div
@ -181,7 +195,7 @@ export default function CodeTabsComponent({
{tab.code}
</SyntaxHighlighter>
</>
) : index === 4 ? (
) : idx === 4 ? (
<>
<div className="api-modal-according-display">
<div
@ -192,8 +206,8 @@ export default function CodeTabsComponent({
: "overflow-hidden"
)}
>
{data?.map((node: any, index) => (
<div className="px-3" key={index}>
{data?.map((node: any, i) => (
<div className="px-3" key={i}>
{tweaks?.tweaksList!.current.includes(
node["data"]["id"]
) && (
@ -236,10 +250,10 @@ export default function CodeTabsComponent({
node.data.node.template[templateField]
.type === "int")
)
.map((templateField, index) => {
.map((templateField, indx) => {
return (
<TableRow
key={index}
key={indx}
className="h-10 dark:border-b-muted"
>
<TableCell className="p-0 text-center text-sm text-foreground">
@ -278,7 +292,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = target;
@ -327,7 +341,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = target;
@ -372,7 +386,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = target;
@ -405,7 +419,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = e;
@ -496,7 +510,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = target;
@ -532,7 +546,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = target;
@ -584,7 +598,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = target;
@ -639,7 +653,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = target;
@ -694,7 +708,7 @@ export default function CodeTabsComponent({
let newInputList =
cloneDeep(old);
newInputList![
index
i
].data.node.template[
templateField
].value = target;

View file

@ -0,0 +1,16 @@
import { fetchErrorComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
export default function FetchErrorComponent({
message,
description,
}: fetchErrorComponentType) {
return (
<div role="status" className="m-auto flex flex-col items-center">
<IconComponent className={`h-16 w-16`} name="Unplug"></IconComponent>
<br></br>
<span className="text-lg text-almost-medium-blue">{message}</span>
<span className="text-lg text-almost-medium-blue">{description}</span>
</div>
);
}

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
strokeWidth={1.5}
className={className}
style={{ color: iconColor }}
/>
);
}
const ForwardedIconComponent = forwardRef(
({ name, className, iconColor }: IconComponentProps, ref) => {
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
return (
<TargetIcon
strokeWidth={1.5}
className={className}
style={iconColor ? { color: iconColor } : {}}
ref={ref}
/>
);
}
);
export default ForwardedIconComponent;

View file

@ -1,14 +1,23 @@
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 { gradients } from "../../utils/styleUtils";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { Separator } from "../ui/separator";
import MenuBar from "./components/menuBar";
@ -17,26 +26,17 @@ export default function Header(): JSX.Element {
const { dark, setDark } = useContext(darkContext);
const { notificationCenter } = useContext(alertContext);
const location = useLocation();
const { logout, autoLogin, isAdmin, userData } = 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} />
)}
@ -119,6 +119,56 @@ 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>
)}
{!autoLogin && (
<>
<Separator orientation="vertical" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className={
"h-7 w-7 rounded-full focus-visible:outline-0 " +
gradients[
parseInt(userData?.id ?? "", 10) % gradients.length
]
}
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
{isAdmin && (
<DropdownMenuItem
className="cursor-pointer"
onClick={() => navigate("/admin")}
>
Admin Page
</DropdownMenuItem>
)}
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
logout();
navigate("/login");
}}
>
Sign Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
)}
</div>
</div>
</div>

View file

@ -30,6 +30,7 @@ export default function InputComponent({
{isForm ? (
<Form.Control asChild>
<Input
type={password && !pwdVisible ? "password" : "text"}
value={value}
disabled={disabled}
required={required}
@ -53,6 +54,7 @@ export default function InputComponent({
</Form.Control>
) : (
<Input
type={password && !pwdVisible ? "password" : "text"}
value={value}
disabled={disabled}
required={required}
@ -76,12 +78,15 @@ export default function InputComponent({
)}
{password && (
<button
type="button"
tabIndex={-1}
className={classNames(
editNode
? "input-component-true-button"
: "input-component-false-button"
)}
onClick={() => {
onClick={(event) => {
event.preventDefault();
setPwdVisible(!pwdVisible);
}}
>

View file

@ -0,0 +1,16 @@
import { Skeleton } from "../ui/skeleton";
export const SkeletonCardComponent = (): JSX.Element => {
return (
<div className="skeleton-card">
<div className="skeleton-card-wrapper">
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-4 w-[120px]" />
</div>
<div className="skeleton-card-text">
<Skeleton className="h-3 w-[250px]" />
<Skeleton className="h-3 w-[200px]" />
</div>
</div>
);
};

View file

@ -0,0 +1,15 @@
import { cn } from "../../utils/utils";
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-border", className)}
{...props}
/>
);
}
export { Skeleton };

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) {
@ -602,3 +615,24 @@ export function tabsArray(codes: string[], method: number) {
},
];
}
export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection.";
export const FETCH_ERROR_DESCRIPION =
"Check if everything is working properly and try again.";
export const BASE_URL_API = "/api/v1/";
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
export const API_PAGE_PARAGRAPH_1 =
"Your secret API keys are listed below. Please note that we do not display your secret API keys again after you generate them.";
export const API_PAGE_PARAGRAPH_2 =
"Do not share your API key with others, or expose it in the browser or other client-side code.";
export const API_PAGE_USER_KEYS =
"This user does not have any keys assigned at the moment.";
export const LAST_USED_SPAN_1 = "The last time this key was used.";
export const LAST_USED_SPAN_2 =
"Accurate to within the hour from the most recent usage.";

View file

@ -48,6 +48,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
const [successOpen, setSuccessOpen] = useState(false);
const [notificationCenter, setNotificationCenter] = useState(false);
const [notificationList, setNotificationList] = useState<AlertItemType[]>([]);
const [isTweakPage, setIsTweakPage] = useState<boolean>(false);
const pushNotificationList = (notification: AlertItemType) => {
setNotificationList((old) => {
let newNotificationList = _.cloneDeep(old);

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 });
@ -37,6 +39,7 @@ const TabsContextInitialValue: TabsContextType = {
save: () => {},
tabId: "",
setTabId: (index: string) => {},
isLoading: true,
flows: [],
removeFlow: (id: string) => {},
addFlow: async (flowData?: any) => "",
@ -45,7 +48,7 @@ const TabsContextInitialValue: TabsContextType = {
downloadFlow: (flow: FlowType) => {},
downloadFlows: () => {},
uploadFlows: () => {},
uploadFlow: () => {},
uploadFlow: async () => "",
isBuilt: false,
setIsBuilt: (state: boolean) => {},
hardReset: () => {},
@ -68,10 +71,14 @@ export const TabsContext = createContext<TabsContextType>(
);
export function TabsProvider({ children }: { children: ReactNode }) {
const { setErrorData, setNoticeData } = useContext(alertContext);
const { setErrorData, setNoticeData, setSuccessData } =
useContext(alertContext);
const { getAuthentication, isAuthenticated } = useContext(AuthContext);
const [tabId, setTabId] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(uid());
const { templates, reactFlowInstance } = useContext(typesContext);
@ -82,6 +89,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const [tabsState, setTabsState] = useState<TabsState>({});
const [getTweak, setTweak] = useState<tweakType>([]);
useEffect(() => {
if (!isAuthenticated) {
hardReset();
}
}, [isAuthenticated]);
const newNodeId = useRef(uid());
function incrementNodeId() {
newNodeId.current = uid();
@ -112,29 +125,33 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
function refreshFlows() {
setIsLoading(true);
getTabsDataFromDB().then((DbData) => {
if (DbData && Object.keys(templates).length > 0) {
try {
processDBData(DbData);
updateStateWithDbData(DbData);
} catch (e) {
console.error(e);
}
setIsLoading(false);
} 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 +160,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
processFlowEdges(flow);
processFlowNodes(flow);
} catch (e) {
console.error(e);
}
} catch (e) {}
});
}
@ -225,6 +240,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
setTabId("");
setFlows([]);
setIsLoading(true);
setId(uid());
}
@ -282,39 +298,38 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* If the file type is application/json, the file is read and parsed into a JSON object.
* The resulting JSON object is passed to the addFlow function.
*/
function uploadFlow(newProject?: boolean, file?: File) {
async function uploadFlow(
newProject?: boolean,
file?: File
): Promise<String | undefined> {
let id;
if (file) {
file.text().then((text) => {
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text);
let text = await file.text();
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text);
addFlow(flow, newProject);
});
id = await addFlow(flow, newProject);
} else {
// create a file input
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
// add a change event listener to the file input
input.onchange = (e: Event) => {
// check if the file type is application/json
if (
(e.target as HTMLInputElement).files![0].type === "application/json"
) {
// get the file from the file input
const currentfile = (e.target as HTMLInputElement).files![0];
// read the file as text
currentfile.text().then((text) => {
// parse the text into a JSON object
id = await new Promise(resolve => {
input.onchange = async (e: Event) => {
if ((e.target as HTMLInputElement).files![0].type === "application/json") {
const currentfile = (e.target as HTMLInputElement).files![0];
let text = await currentfile.text();
let flow: FlowType = JSON.parse(text);
addFlow(flow, newProject);
});
}
};
// trigger the file input click event to open the file dialog
input.click();
const flowId = await addFlow(flow, newProject);
resolve(flowId);
}
};
// trigger the file input click event to open the file dialog
input.click();
});
}
return id;
}
function uploadFlows() {
@ -478,7 +493,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 +593,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 +616,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
} catch (err) {
setErrorData(err as errorsVarType);
setErrorData({
title: "Error while saving changes",
list: [(err as AxiosError).message],
});
}
}
@ -634,6 +652,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
paste,
getTweak,
setTweak,
isLoading,
}}
>
{children}

View file

@ -6,10 +6,11 @@ import {
useState,
} from "react";
import { Node, ReactFlowInstance } from "reactflow";
import { getAll } from "../controllers/API";
import { getAll, getHealth } from "../controllers/API";
import { APIKindType } from "../types/api";
import { typesContextType } from "../types/typesContext";
import { alertContext } from "./alertContext";
import { AuthContext } from "./authContext";
//context to share types adn functions from nodes to flow
@ -23,6 +24,8 @@ const initialValue: typesContextType = {
setTemplates: () => {},
data: {},
setData: () => {},
setFetchError: () => {},
fetchError: false,
};
export const typesContext = createContext<typesContextType>(initialValue);
@ -33,67 +36,58 @@ export function TypesProvider({ children }: { children: ReactNode }) {
useState<ReactFlowInstance | null>(null);
const [templates, setTemplates] = useState({});
const [data, setData] = useState({});
const [fetchError, setFetchError] = useState(false);
const { setLoading } = useContext(alertContext);
const { getAuthentication } = useContext(AuthContext);
useEffect(() => {
let delay = 1000; // Start delay of 1 second
let intervalId: NodeJS.Timer;
let retryCount = 0; // Count of retry attempts
const maxRetryCount = 5; // Max retry attempts
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
if (getAuthentication() === true) {
getTypes();
}
}, [getAuthentication()]);
async function getTypes(): Promise<void> {
// We will keep a flag to handle the case where the component is unmounted before the API call resolves.
let isMounted = true;
async function getTypes(): Promise<void> {
try {
const result = await getAll();
// Make sure to only update the state if the component is still mounted.
if (isMounted) {
setLoading(false);
setData(result.data);
setTemplates(
Object.keys(result.data).reduce((acc, curr) => {
try {
const result = await getAll();
// Make sure to only update the state if the component is still mounted.
if (isMounted && result?.status === 200) {
setLoading(false);
setData(result.data);
setTemplates(
Object.keys(result.data).reduce((acc, curr) => {
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
acc[c] = result.data[curr][c];
});
return acc;
}, {})
);
// Set the types by reducing over the keys of the result data and updating the accumulator.
setTypes(
// Reverse the keys so the tool world does not overlap
Object.keys(result.data)
.reverse()
.reduce((acc, curr) => {
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
acc[c] = result.data[curr][c];
acc[c] = curr;
// Add the base classes to the accumulator as well.
result.data[curr][c].base_classes?.forEach((b) => {
acc[b] = curr;
});
});
return acc;
}, {})
);
// Set the types by reducing over the keys of the result data and updating the accumulator.
setTypes(
// Reverse the keys so the tool world does not overlap
Object.keys(result.data)
.reverse()
.reduce((acc, curr) => {
Object.keys(result.data[curr]).forEach(
(c: keyof APIKindType) => {
acc[c] = curr;
// Add the base classes to the accumulator as well.
result.data[curr][c].base_classes?.forEach((b) => {
acc[b] = curr;
});
}
);
return acc;
}, {})
);
}
// Clear the interval if successful.
clearInterval(intervalId!);
} catch (error) {
console.error("An error has occurred while fetching types.");
);
}
} catch (error) {
console.error("An error has occurred while fetching types.");
await getHealth().catch((e) => {
setFetchError(true);
});
}
// Start the initial interval.
intervalId = setInterval(getTypes, delay);
return () => {
// This will clear the interval when the component unmounts, or when the dependencies of the useEffect hook change.
clearInterval(intervalId!);
// Indicate that the component has been unmounted.
isMounted = false;
};
}, []);
}
function deleteNode(idx: string) {
reactFlowInstance!.setNodes(
@ -117,6 +111,8 @@ export function TypesProvider({ children }: { children: ReactNode }) {
templates,
data,
setData,
fetchError,
setFetchError,
}}
>
{children}

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,22 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 841.89 595.28" style="enable-background:new 0 0 841.89 595.28;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#505AA5;}
</style>
<path class="st0" d="M349.6,124.45c48.94-54.99,129.98-71.12,196.61-39.38c88.52,42.17,120.82,149.6,72.62,232.48L510.41,503.76
c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l131.26-225.49c34.97-60.15,11.58-138.11-52.6-168.8
c-48.16-23.03-107.02-11.53-142.6,28.08c-19.62,21.74-30.64,49.82-31.01,79.01c-0.37,29.2,9.94,57.53,29.01,79.76
c3.43,3.99,7.12,7.75,11.04,11.25l-76.63,131.88c-3,5.16-6.99,9.67-11.74,13.3c-4.76,3.62-10.18,6.28-15.97,7.82
c-5.79,1.54-11.83,1.93-17.77,1.16c-5.94-0.78-11.67-2.71-16.87-5.68l83.19-143.17c-11.95-17.11-20.53-36.31-25.29-56.58
L261.1,360.8c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l131.83-226.76
C336.06,141.32,342.43,132.55,349.6,124.45z M501.76,196.63c31.75,18.21,42.71,58.7,24.34,90.22L399.69,503.74
c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l117.38-201.93c-9.42-1.97-18.29-5.94-26.01-11.65
c-7.72-5.71-14.1-13.01-18.7-21.4c-4.6-8.4-7.31-17.68-7.95-27.22c-0.64-9.54,0.82-19.1,4.27-28.02c3.45-8.92,8.8-17,15.7-23.67
c6.9-6.67,15.17-11.77,24.24-14.95c9.08-3.18,18.74-4.37,28.32-3.49S493.43,191.83,501.76,196.63z M455.78,237.39
c-2.17,1.66-4,3.72-5.36,6.08h-0.01c-2.06,3.55-3.02,7.62-2.75,11.71c0.27,4.09,1.76,8,4.27,11.25c2.51,3.25,5.94,5.69,9.84,7
c3.91,1.32,8.12,1.45,12.1,0.39c3.99-1.06,7.56-3.27,10.28-6.35c2.72-3.08,4.46-6.89,5-10.95c0.54-4.06-0.15-8.19-1.97-11.87
c-1.82-3.67-4.71-6.73-8.28-8.78c-2.37-1.36-4.99-2.24-7.71-2.6c-2.72-0.35-5.48-0.18-8.12,0.53
C460.43,234.52,457.95,235.73,455.78,237.39z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 841.89 595.28" style="enable-background:new 0 0 841.89 595.28;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#505AA5;}
</style>
<path class="st0" d="M349.6,124.45c48.94-54.99,129.98-71.12,196.61-39.38c88.52,42.17,120.82,149.6,72.62,232.48L510.41,503.76
c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l131.26-225.49c34.97-60.15,11.58-138.11-52.6-168.8
c-48.16-23.03-107.02-11.53-142.6,28.08c-19.62,21.74-30.64,49.82-31.01,79.01c-0.37,29.2,9.94,57.53,29.01,79.76
c3.43,3.99,7.12,7.75,11.04,11.25l-76.63,131.88c-3,5.16-6.99,9.67-11.74,13.3c-4.76,3.62-10.18,6.28-15.97,7.82
c-5.79,1.54-11.83,1.93-17.77,1.16c-5.94-0.78-11.67-2.71-16.87-5.68l83.19-143.17c-11.95-17.11-20.53-36.31-25.29-56.58
L261.1,360.8c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l131.83-226.76
C336.06,141.32,342.43,132.55,349.6,124.45z M501.76,196.63c31.75,18.21,42.71,58.7,24.34,90.22L399.69,503.74
c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l117.38-201.93c-9.42-1.97-18.29-5.94-26.01-11.65
c-7.72-5.71-14.1-13.01-18.7-21.4c-4.6-8.4-7.31-17.68-7.95-27.22c-0.64-9.54,0.82-19.1,4.27-28.02c3.45-8.92,8.8-17,15.7-23.67
c6.9-6.67,15.17-11.77,24.24-14.95c9.08-3.18,18.74-4.37,28.32-3.49S493.43,191.83,501.76,196.63z M455.78,237.39
c-2.17,1.66-4,3.72-5.36,6.08h-0.01c-2.06,3.55-3.02,7.62-2.75,11.71c0.27,4.09,1.76,8,4.27,11.25c2.51,3.25,5.94,5.69,9.84,7
c3.91,1.32,8.12,1.45,12.1,0.39c3.99-1.06,7.56-3.27,10.28-6.35c2.72-3.08,4.46-6.89,5-10.95c0.54-4.06-0.15-8.19-1.97-11.87
c-1.82-3.67-4.71-6.73-8.28-8.78c-2.37-1.36-4.99-2.24-7.71-2.6c-2.72-0.35-5.48-0.18-8.12,0.53
C460.43,234.52,457.95,235.73,455.78,237.39z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

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

@ -28,7 +28,8 @@ export default function CodeAreaModal({
const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext);
const [height, setHeight] = useState<string | null>(null);
const { setErrorData, setSuccessData } = useContext(alertContext);
const { setErrorData, setSuccessData, isTweakPage } =
useContext(alertContext);
const [error, setError] = useState<{
detail: { error: string | undefined; traceback: string | undefined };
} | null>(null);
@ -39,7 +40,6 @@ export default function CodeAreaModal({
if (dynamic && Object.keys(nodeClass!.template).length > 2) {
return;
}
processCode();
}, []);
function processNonDynamicField() {

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

@ -23,7 +23,9 @@ 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 { getBuildStatus } from "../../controllers/API";
import { TabsState } from "../../types/tabs";
import { validateNodes } from "../../utils/reactflowUtils";
@ -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,28 @@ 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
@ -173,7 +190,7 @@ export default function FormModal({
return `${
isDevelopment ? "ws" : webSocketProtocol
}://${host}${chatEndpoint}`;
}://${host}${chatEndpoint}?token=${encodeURIComponent(accessToken!)}`;
}
function handleWsMessage(data: any) {
@ -258,7 +275,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 +282,6 @@ export default function FormModal({
handleOnClose(event);
};
newWs.onerror = (ev) => {
console.log(ev, "error");
if (flow.id === "") {
connectWS();
} else {
@ -292,7 +307,6 @@ export default function FormModal({
useEffect(() => {
connectWS();
return () => {
console.log("unmount");
console.log(ws);
if (ws.current) {
ws.current.close();

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,11 @@ 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 +152,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,12 @@
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 Header from "../../components/headerComponent";
import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { Input } from "../../components/ui/input";
import {
Table,
@ -14,387 +16,421 @@ 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 { Users } from "../../types/api";
import { UserInputType } from "../../types/components";
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>
</>
)}
{userList.current.length > 0 && (
<>
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
<Input
value={inputValue}
placeholder="Filter users..."
className="h-8 w-[150px] lg:w-[250px]"
onChange={(e) => handleFilterUsers(e.target.value)}
/>
{inputValue.length > 0 && (
<Button
onClick={() => {
resetFilter();
setInputValue("");
}}
variant="ghost"
className="h-8 px-2 lg:px-3"
>
Reset
<X className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<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>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}}
>
<Button>New User</Button>
</UserManagementModal>
<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>
<div className="flex items-center space-x-2"></div>
</div>
<div
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted
custom-scroll"
>
<Table className="table-fixed bg-muted outline-1 ">
<TableHeader>
<TableRow>
<TableHead className="h-10">User</TableHead>
<TableHead className="h-10">Password</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>
<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(index);
}}
>
<ShadTooltip content="Delete" side="top">
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</ConfirmationModal>
</div>
</TableCell>
{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">
<Input
value={inputValue}
placeholder="Filter users..."
className="h-8 w-[150px] lg:w-[250px]"
onChange={(e) => handleFilterUsers(e.target.value)}
/>
{inputValue.length > 0 && (
<Button
onClick={() => {
setInputValue("");
setFilterUserList(userList.current);
}}
variant="ghost"
className="h-8 px-2 lg:px-3"
>
Reset
<X className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}}
>
<Button>New User</Button>
</UserManagementModal>
</div>
</div>
{loadingUsers && (
<div>
<strong>Loading...</strong>
</div>
)}
<div
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
className={
loadingUsers
? "hidden"
: "table-fixed bg-muted outline-1"
}
>
<TableRow>
<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>
))}
</TableBody>
</Table>
</div>
<PaginatorComponent
pageIndex={index}
pageSize={size}
totalRowsCount={filterUserList.length}
paginate={(pageIndex, pageSize) => {
handleChangePagination(pageSize, pageIndex);
}}
></PaginatorComponent>
</>
)}
</TableHeader>
{!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="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) => {
handleDisableUser(
user.is_active,
user.id,
user
);
}}
>
<Checkbox
id="is_active"
checked={user.is_active}
/>
</ConfirmationModal>
</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={totalRowsCount}
paginate={(pageIndex, pageSize) => {
handleChangePagination(pageSize, pageIndex);
}}
></PaginatorComponent>
</>
</div>
</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

@ -121,7 +121,6 @@ export default function ExtraSidebar(): JSX.Element {
}
onClick={(event) => {
saveFlow(flow!);
setSuccessData({ title: "Changes saved successfully" });
}}
>
<IconComponent

View file

@ -1,14 +1,33 @@
import { useContext, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import DropdownButton from "../../components/DropdownButtonComponent";
import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext";
export default function HomePage(): JSX.Element {
const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow } =
useContext(TabsContext);
const {
flows,
setTabId,
downloadFlows,
uploadFlows,
addFlow,
removeFlow,
uploadFlow,
isLoading,
} = useContext(TabsContext);
const dropdownOptions = [
{
name: "Import from JSON",
onBtnClick: () =>
uploadFlow(true).then((id) => {
navigate("/flow/" + id);
}),
},
];
// Set a null id
useEffect(() => {
@ -16,6 +35,10 @@ export default function HomePage(): JSX.Element {
}, []);
const navigate = useNavigate();
useEffect(() => {
console.log(isLoading);
}, [isLoading]);
// Personal flows display
return (
<>
@ -45,48 +68,55 @@ export default function HomePage(): JSX.Element {
<IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection
</Button>
<Button
variant="primary"
onClick={() => {
<DropdownButton
firstButtonName="New Project"
onFirstBtnClick={() => {
addFlow(null!, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
options={dropdownOptions}
/>
</div>
</div>
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span>
<div className="main-page-flows-display">
{flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))}
{isLoading && flows.length == 0 ? (
<>
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
<SkeletonCardComponent />
</>
) : (
flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))
)}
</div>
</div>
</>

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,50 @@ 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 +70,7 @@ export default function LoginPage(): JSX.Element {
event.preventDefault();
return;
}
signIn();
const data = Object.fromEntries(new FormData(event.currentTarget));
event.preventDefault();
}}
@ -48,6 +90,7 @@ export default function LoginPage(): JSX.Element {
<Form.Control asChild>
<Input
type="username"
onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } });
}}
@ -88,12 +131,14 @@ export default function LoginPage(): JSX.Element {
</div>
<div className="w-full">
<Form.Submit asChild>
<Button className="mr-3 mt-6 w-full">Sign in</Button>
<Button className="mr-3 mt-6 w-full" type="submit">
Sign in
</Button>
</Form.Submit>
</div>
<div className="w-full">
<Link to="">
<Button className="w-full" variant="outline">
<Link to="/signup">
<Button className="w-full" variant="outline" type="button">
Don't have an account?&nbsp;<b>Sign Up</b>
</Button>
</Link>

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>) => {
@ -48,6 +84,7 @@ export default function SignUp(): JSX.Element {
<Form.Control asChild>
<Input
type="username"
onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } });
}}
@ -120,7 +157,15 @@ 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
type="submit"
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

@ -126,6 +126,18 @@
@apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
}
.skeleton-card {
@apply bg-background h-48 p-4 border rounded-lg flex flex-col gap-6;
}
.skeleton-card-wrapper {
@apply flex items-center space-x-4;
}
.skeleton-card-text {
@apply flex flex-col gap-3;
}
/* The same as primary-input but no-truncate */
.textarea-primary {
@apply form-input block w-full rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
@ -488,10 +500,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

@ -36,3 +36,18 @@ pre {
.gradient-start {
animation: gradient-motion-start 4s infinite forwards;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus {
-webkit-text-fill-color: black;
-webkit-box-shadow: 0 0 0px 1000px #fff6d0 inset;
box-shadow: 0 0 0px 1000px #fff6d0 inset;
color: black;
}

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

@ -218,7 +218,7 @@ export type signUpInputStateType = {
export type inputHandlerEventType = {
target: {
value: string;
value: string | boolean;
name: string;
};
};
@ -261,6 +261,29 @@ export type loginInputStateType = {
password: string;
};
export type UserInputType = {
username: string;
password: string;
is_active?: boolean;
is_superuser?: boolean;
id?: string;
create_at?: string;
updated_at?: string;
};
export type ApiKeyType = {
title: string;
cancelText: string;
confirmationText: string;
children: ReactElement;
icon: string;
data?: any;
onCloseModal: () => void;
};
export type ApiKeyInputType = {
apikeyname: string;
};
export type groupedObjType = {
family: string;
type: string;
@ -508,3 +531,22 @@ 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;
};
export type dropdownButtonPropsType = {
firstButtonName: string;
onFirstBtnClick: () => void;
options: Array<{ name: string; onBtnClick: () => void; }>;
};

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

@ -5,6 +5,7 @@ export type TabsContextType = {
saveFlow: (flow: FlowType) => Promise<void>;
save: () => void;
tabId: string;
isLoading: boolean;
setTabId: (index: string) => void;
flows: Array<FlowType>;
removeFlow: (id: string) => void;
@ -23,7 +24,7 @@ export type TabsContextType = {
uploadFlows: () => void;
isBuilt: boolean;
setIsBuilt: (state: boolean) => void;
uploadFlow: (newFlow?: boolean, file?: File) => void;
uploadFlow: (newFlow?: boolean, file?: File) => Promise<String | undefined>;
hardReset: () => void;
getNodeId: (nodeType: string) => string;
tabsState: TabsState;

View file

@ -16,6 +16,8 @@ export type typesContextType = {
setTemplates: (newState: {}) => void;
data: APIDataType;
setData: (newState: {}) => void;
fetchError: boolean;
setFetchError: (newState: boolean) => void;
};
export type alertContextType = {
@ -44,6 +46,8 @@ export type alertContextType = {
export type darkContextType = {
dark: {};
setDark: (newState: {}) => void;
stars: number;
setStars: (stars: number) => void;
};
export type locationContextType = {

View file

@ -1,4 +1,4 @@
import { Edge } from "reactflow";
import { Edge, Node } from "reactflow";
import { NodeType } from "../flow";
export type cleanEdgesType = {
@ -8,3 +8,8 @@ export type cleanEdgesType = {
};
updateEdge: (edge: Edge[]) => void;
};
export type unselectAllNodesType = {
updateNodes: (nodes: Node[]) => void;
data: Node[] | null;
};

View file

@ -2,13 +2,17 @@ import _ from "lodash";
import {
Connection,
Edge,
Node,
ReactFlowInstance,
ReactFlowJsonObject,
} from "reactflow";
import { specialCharsRegex } from "../constants/constants";
import { APITemplateType } from "../types/api";
import { FlowType, NodeType } from "../types/flow";
import { cleanEdgesType } from "../types/utils/reactflowUtils";
import {
cleanEdgesType,
unselectAllNodesType,
} from "../types/utils/reactflowUtils";
import { toNormalCase } from "./utils";
export function cleanEdges({
@ -55,6 +59,14 @@ export function cleanEdges({
updateEdge(newEdges);
}
export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
let newNodes = _.cloneDeep(data);
newNodes!.forEach((node: Node) => {
node.selected = false;
});
updateNodes(newNodes!);
}
export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection,
reactFlowInstance: ReactFlowInstance
@ -247,7 +259,6 @@ export function handleKeyDown(
inputValue: string | string[] | null,
block: string
) {
console.log(e, inputValue, block);
//condition to fix bug control+backspace on Windows/Linux
if (
(typeof inputValue === "string" &&

View file

@ -5,6 +5,7 @@ import {
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronUp,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
@ -19,6 +20,8 @@ import {
Edit,
Eraser,
ExternalLink,
Eye,
EyeOff,
File,
FileDown,
FileSearch,
@ -33,6 +36,7 @@ import {
HelpCircle,
Home,
Info,
Key,
Laptop2,
Layers,
Lightbulb,
@ -59,7 +63,9 @@ import {
TerminalSquare,
Trash2,
Undo,
Unplug,
Upload,
UserCog2,
UserMinus2,
UserPlus2,
Users2,
@ -290,4 +296,10 @@ export const nodeIconsLucide: iconsType = {
ChevronsLeft,
FaGithub,
FaApple,
EyeOff,
Eye,
UserCog2,
Key,
Unplug,
ChevronUp,
};

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"),