Login (#802)
### Description This pull request introduces a significant enhancement to the project by incorporating robust authentication logic. The enhancement addresses the critical need for secure user access and data protection within the application. With this addition, the system will be able to authenticate users, verify their identities, and provide access only to authorized individuals. ### Changes Implemented - **Authentication Middleware**: A new middleware module has been implemented to intercept incoming requests and validate user authentication. This middleware checks for valid authentication tokens or credentials and ensures that only authenticated users can access protected resources. - **User Authentication Endpoint**: A new endpoint has been created to handle user authentication. This endpoint allows users to provide their credentials, which are then securely processed and validated against the stored user data. - **Password Hashing**: User passwords are now securely hashed using a strong cryptographic algorithm before being stored in the database. This ensures that even if the database is compromised, user passwords remain inaccessible. - **Token-Based Authentication**: Upon successful authentication, the system generates a time-limited access token for the user. This token must be included in subsequent requests to access protected resources. Token validity is maintained through token expiration and refresh mechanisms. - **Authorization Checks**: The logic has been extended to include authorization checks after successful authentication. This ensures that authenticated users only have access to the resources they are authorized to use, based on their roles and permissions. ### Benefits 1. **Enhanced Security**: By implementing strong authentication mechanisms and password hashing, the project significantly improves security, minimizing the risk of unauthorized access or data breaches. 2. **User-Friendly Experience**: Users can securely access the application, confident in the knowledge that their credentials and data are protected. The token-based authentication simplifies the user experience by eliminating the need for frequent re-authentication. 3. **Scalability**: The authentication logic has been designed with scalability in mind, ensuring that the system can handle a growing number of users and requests without compromising security or performance. 4. **Modularity**: The new authentication logic has been integrated as a separate module, enhancing the project's modularity and maintainability.
This commit is contained in:
commit
02cc23d6b1
110 changed files with 4566 additions and 1804 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import _ from "lodash";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import "reactflow/dist/style.css";
|
||||
import "./App.css";
|
||||
|
||||
|
|
@ -42,8 +42,11 @@ export default function App() {
|
|||
successData,
|
||||
successOpen,
|
||||
setSuccessOpen,
|
||||
setErrorData,
|
||||
loading,
|
||||
setLoading,
|
||||
} = useContext(alertContext);
|
||||
const navigate = useNavigate();
|
||||
const { fetchError } = useContext(typesContext);
|
||||
|
||||
// Initialize state variable for the list of alerts
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -12,17 +12,21 @@ import { Button } from "../ui/button";
|
|||
|
||||
export default function PaginatorComponent({
|
||||
pageSize = 10,
|
||||
pageIndex = 1,
|
||||
rowsCount = [10, 20, 30],
|
||||
pageIndex = 0,
|
||||
rowsCount = [10, 20, 50, 100],
|
||||
totalRowsCount = 0,
|
||||
paginate,
|
||||
}: PaginatorComponentType) {
|
||||
const [size, setPageSize] = useState(pageSize);
|
||||
const [index, setPageIndex] = useState(pageIndex);
|
||||
|
||||
const [maxIndex, setMaxPageIndex] = useState(
|
||||
Math.ceil(totalRowsCount / pageSize)
|
||||
);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
setMaxPageIndex(Math.ceil(totalRowsCount / size));
|
||||
}, [totalRowsCount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -35,7 +39,7 @@ export default function PaginatorComponent({
|
|||
onValueChange={(pageSize: string) => {
|
||||
setPageSize(Number(pageSize));
|
||||
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
|
||||
paginate(Number(pageSize), index);
|
||||
paginate(Number(pageSize), 0);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[100px]">
|
||||
|
|
@ -51,30 +55,30 @@ export default function PaginatorComponent({
|
|||
</Select>
|
||||
</div>
|
||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {index} of {maxIndex}
|
||||
Page {currentPage} of {maxIndex}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
disabled={index <= 0}
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => {
|
||||
setPageIndex(1);
|
||||
paginate(size, 1);
|
||||
setPageIndex(0);
|
||||
setCurrentPage(1);
|
||||
paginate(size, 0);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={index <= 0}
|
||||
onClick={() => {
|
||||
if (index <= 1) {
|
||||
setPageIndex(1);
|
||||
paginate(size, 1);
|
||||
} else {
|
||||
{
|
||||
setPageIndex(index - 1);
|
||||
paginate(size, index - 1);
|
||||
}
|
||||
if (index > 0) {
|
||||
const pgIndex = size - index;
|
||||
setCurrentPage(currentPage - 1);
|
||||
setPageIndex(pgIndex);
|
||||
paginate(size, pgIndex);
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
|
|
@ -84,14 +88,12 @@ export default function PaginatorComponent({
|
|||
<IconComponent name="ChevronLeft" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={currentPage === maxIndex}
|
||||
onClick={() => {
|
||||
if (index >= maxIndex) {
|
||||
setPageIndex(maxIndex);
|
||||
paginate(size, maxIndex);
|
||||
} else {
|
||||
setPageIndex(index + 1);
|
||||
paginate(size, index + 1);
|
||||
}
|
||||
const pgIndex = size + index;
|
||||
setPageIndex(pgIndex);
|
||||
setCurrentPage(currentPage + 1);
|
||||
paginate(size, pgIndex);
|
||||
}}
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0"
|
||||
|
|
@ -100,11 +102,13 @@ export default function PaginatorComponent({
|
|||
<IconComponent name="ChevronRight" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={currentPage === maxIndex}
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => {
|
||||
setPageIndex(maxIndex);
|
||||
paginate(size, maxIndex);
|
||||
setPageIndex(maxIndex - 1);
|
||||
setCurrentPage(maxIndex);
|
||||
paginate(size, size);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
|
|
|
|||
30
src/frontend/src/components/authAdminGuard/index.tsx
Normal file
30
src/frontend/src/components/authAdminGuard/index.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { useContext, useEffect } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
export const ProtectedAdminRoute = ({ children }) => {
|
||||
const {
|
||||
isAdmin,
|
||||
isAuthenticated,
|
||||
logout,
|
||||
getAuthentication,
|
||||
userData,
|
||||
autoLogin,
|
||||
} = useContext(AuthContext);
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated && !getAuthentication()) {
|
||||
window.location.replace("/login");
|
||||
logout();
|
||||
}
|
||||
}, [isAuthenticated, getAuthentication, logout, userData]);
|
||||
|
||||
if (!isAuthenticated && !getAuthentication()) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
if ((userData && !isAdmin) || autoLogin) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
14
src/frontend/src/components/authGuard/index.tsx
Normal file
14
src/frontend/src/components/authGuard/index.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useContext } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
export const ProtectedRoute = ({ children }) => {
|
||||
const { isAuthenticated, logout, getAuthentication } =
|
||||
useContext(AuthContext);
|
||||
if (!isAuthenticated && !getAuthentication()) {
|
||||
logout();
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
19
src/frontend/src/components/authLoginGuard/index.tsx
Normal file
19
src/frontend/src/components/authLoginGuard/index.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { useContext } from "react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
export const ProtectedLoginRoute = ({ children }) => {
|
||||
const { getAuthentication, autoLogin } = useContext(AuthContext);
|
||||
|
||||
if (autoLogin === true) {
|
||||
window.location.replace("/");
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
if (getAuthentication()) {
|
||||
window.location.replace("/");
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
13
src/frontend/src/components/catchAllRoutes/index.tsx
Normal file
13
src/frontend/src/components/catchAllRoutes/index.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const CatchAllRoute = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Redirect to the root ("/") when the catch-all route is matched
|
||||
useEffect(() => {
|
||||
navigate("/");
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
import { forwardRef } from "react";
|
||||
import { IconComponentProps } from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
|
||||
export default function IconComponent({
|
||||
name,
|
||||
className,
|
||||
iconColor,
|
||||
}: IconComponentProps): JSX.Element {
|
||||
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
||||
return (
|
||||
<TargetIcon
|
||||
className={className}
|
||||
style={{ color: iconColor }}
|
||||
stroke-width={1.5}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const ForwardedIconComponent = forwardRef(
|
||||
({ name, className, iconColor }: IconComponentProps, ref) => {
|
||||
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
||||
return (
|
||||
<TargetIcon
|
||||
strokeWidth={1.5}
|
||||
className={className}
|
||||
style={iconColor ? { color: iconColor } : {}}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ForwardedIconComponent;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { useContext, useEffect, useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import AlertDropdown from "../../alerts/alertDropDown";
|
||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { darkContext } from "../../contexts/darkContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { getRepoStars } from "../../controllers/API";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { Button } from "../ui/button";
|
||||
import { Separator } from "../ui/separator";
|
||||
|
|
@ -17,29 +17,54 @@ export default function Header(): JSX.Element {
|
|||
const { dark, setDark } = useContext(darkContext);
|
||||
const { notificationCenter } = useContext(alertContext);
|
||||
const location = useLocation();
|
||||
const { logout, autoLogin, isAdmin } = useContext(AuthContext);
|
||||
const { stars } = useContext(darkContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [stars, setStars] = useState(null);
|
||||
|
||||
// Get and set numbers of stars on header
|
||||
useEffect(() => {
|
||||
async function fetchStars() {
|
||||
const starsCount = await getRepoStars("logspace-ai", "langflow");
|
||||
setStars(starsCount);
|
||||
}
|
||||
fetchStars();
|
||||
}, []);
|
||||
return (
|
||||
<div className="header-arrangement">
|
||||
<div className="header-start-display">
|
||||
<Link to="/">
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
<Button variant="outline" className="">
|
||||
Sign out
|
||||
</Button>
|
||||
|
||||
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
|
||||
<MenuBar flows={flows} tabId={tabId} />
|
||||
)}
|
||||
{!autoLogin && location.pathname !== `/flow/${tabId}` && (
|
||||
<a
|
||||
onClick={() => {
|
||||
logout();
|
||||
navigate("/login");
|
||||
}}
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer mx-5"
|
||||
>
|
||||
Sign out
|
||||
</a>
|
||||
)}
|
||||
|
||||
{location.pathname === "/admin" && (
|
||||
<a
|
||||
onClick={() => {
|
||||
navigate("/");
|
||||
}}
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
)}
|
||||
|
||||
{isAdmin &&
|
||||
!autoLogin &&
|
||||
location.pathname !== "/admin" &&
|
||||
location.pathname !== `/flow/${tabId}` && (
|
||||
<a
|
||||
className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer"
|
||||
onClick={() => navigate("/admin")}
|
||||
>
|
||||
Admin page
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="round-button-div">
|
||||
<Link to="/">
|
||||
|
|
@ -119,6 +144,18 @@ export default function Header(): JSX.Element {
|
|||
/>
|
||||
</div>
|
||||
</AlertDropdown>
|
||||
{!autoLogin && (
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate("/account/api-keys");
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name="Key"
|
||||
className="side-bar-button-size text-muted-foreground hover:text-accent-foreground"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ export default function InputComponent({
|
|||
? "input-component-true-button"
|
||||
: "input-component-false-button"
|
||||
)}
|
||||
onClick={() => {
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
setPwdVisible(!pwdVisible);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -508,6 +508,7 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
|
|||
"/api/v1/validate/code",
|
||||
"/api/v1/custom_component",
|
||||
"/api/v1/validate/prompt",
|
||||
"http://localhost:7860/login",
|
||||
];
|
||||
|
||||
export const skipNodeUpdate = ["CustomComponent"];
|
||||
|
|
@ -522,6 +523,18 @@ export const CONTROL_LOGIN_STATE = {
|
|||
username: "",
|
||||
password: "",
|
||||
};
|
||||
|
||||
export const CONTROL_NEW_USER = {
|
||||
username: "",
|
||||
password: "",
|
||||
is_active: false,
|
||||
is_superuser: false,
|
||||
};
|
||||
|
||||
export const CONTROL_NEW_API_KEY = {
|
||||
apikeyname: "",
|
||||
};
|
||||
|
||||
export const tabsCode = [];
|
||||
|
||||
export function tabsArray(codes: string[], method: number) {
|
||||
|
|
@ -605,3 +618,21 @@ export function tabsArray(codes: string[], method: number) {
|
|||
export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection.";
|
||||
export const FETCH_ERROR_DESCRIPION =
|
||||
"Check if everything is working properly and try again.";
|
||||
|
||||
export const BASE_URL_API = "/api/v1/";
|
||||
|
||||
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
||||
|
||||
export const API_PAGE_PARAGRAPH_1 =
|
||||
"Your secret API keys are listed below. Please note that we do not display your secret API keys again after you generate them.";
|
||||
|
||||
export const API_PAGE_PARAGRAPH_2 =
|
||||
"Do not share your API key with others, or expose it in the browser or other client-side code.";
|
||||
|
||||
export const API_PAGE_USER_KEYS =
|
||||
"This user does not have any keys assigned at the moment.";
|
||||
|
||||
export const LAST_USED_SPAN_1 = "The last time this key was used.";
|
||||
|
||||
export const LAST_USED_SPAN_2 =
|
||||
"Accurate to within the hour from the most recent usage.";
|
||||
|
|
|
|||
|
|
@ -1,74 +1,120 @@
|
|||
import { createContext, useEffect, useState } from "react";
|
||||
import { AuthContextType, userData } from "../types/contexts/auth";
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
import Cookies from "universal-cookie";
|
||||
import { autoLogin as autoLoginApi, getLoggedUser } from "../controllers/API";
|
||||
import { Users } from "../types/api";
|
||||
import { AuthContextType } from "../types/contexts/auth";
|
||||
import { alertContext } from "./alertContext";
|
||||
|
||||
const initialValue: AuthContextType = {
|
||||
isAdmin: false,
|
||||
setIsAdmin: () => false,
|
||||
isAuthenticated: false,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
refreshAccessToken: () => Promise.resolve(),
|
||||
userData: null,
|
||||
setUserData: () => {},
|
||||
getAuthentication: () => false,
|
||||
authenticationErrorCount: 0,
|
||||
autoLogin: false,
|
||||
setAutoLogin: () => {},
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextType>(initialValue);
|
||||
export const AuthContext = createContext<AuthContextType>(initialValue);
|
||||
|
||||
export function AuthProvider({ children }): React.ReactElement {
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
const [userData, setUserData] = useState<userData | null>(null);
|
||||
|
||||
const cookies = new Cookies();
|
||||
const [accessToken, setAccessToken] = useState<string | null>(
|
||||
cookies.get("access_token")
|
||||
);
|
||||
const [refreshToken, setRefreshToken] = useState<string | null>(
|
||||
cookies.get("refresh_token")
|
||||
);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
||||
const [userData, setUserData] = useState<Users | null>(null);
|
||||
const [autoLogin, setAutoLogin] = useState<boolean>(false);
|
||||
const { setLoading } = useContext(alertContext);
|
||||
useEffect(() => {
|
||||
const storedAccessToken = localStorage.getItem("access_token");
|
||||
const storedAccessToken = cookies.get("access_token");
|
||||
if (storedAccessToken) {
|
||||
setAccessToken(storedAccessToken);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const isLoginPage = location.pathname.includes("login");
|
||||
|
||||
autoLoginApi()
|
||||
.then((user) => {
|
||||
if (user && user["access_token"]) {
|
||||
user["refresh_token"] = "auto";
|
||||
login(user["access_token"], user["refresh_token"]);
|
||||
setUserData(user);
|
||||
setAutoLogin(true);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setAutoLogin(false);
|
||||
if (getAuthentication() && !isLoginPage) {
|
||||
getLoggedUser()
|
||||
.then((user) => {
|
||||
setUserData(user);
|
||||
setLoading(false);
|
||||
const isSuperUser = user.is_superuser;
|
||||
setIsAdmin(isSuperUser);
|
||||
})
|
||||
.catch((error) => {});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
function getAuthentication() {
|
||||
const storedRefreshToken = cookies.get("refresh_token");
|
||||
const storedAccess = cookies.get("access_token");
|
||||
const auth = storedAccess && storedRefreshToken ? true : false;
|
||||
return auth;
|
||||
}
|
||||
|
||||
function login(newAccessToken: string, refreshToken: string) {
|
||||
localStorage.setItem("access_token", newAccessToken);
|
||||
cookies.set("access_token", newAccessToken, { path: "/" });
|
||||
cookies.set("refresh_token", refreshToken, { path: "/" });
|
||||
setAccessToken(newAccessToken);
|
||||
// Store refreshToken if needed
|
||||
setRefreshToken(refreshToken);
|
||||
setIsAuthenticated(true);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem("access_token");
|
||||
// Clear refreshToken if used
|
||||
cookies.remove("access_token", { path: "/" });
|
||||
cookies.remove("refresh_token", { path: "/" });
|
||||
setIsAdmin(false);
|
||||
setUserData(null);
|
||||
setAccessToken(null);
|
||||
}
|
||||
|
||||
async function refreshAccessToken(refreshToken: string) {
|
||||
try {
|
||||
// Call your API to refresh the access token using the refresh token
|
||||
const response = await fetch("/api/refresh-token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
login(data.accessToken, refreshToken);
|
||||
} else {
|
||||
logout();
|
||||
}
|
||||
} catch (error) {
|
||||
logout();
|
||||
}
|
||||
setRefreshToken(null);
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
|
||||
return (
|
||||
// !! to convert string to boolean
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
isAdmin,
|
||||
setIsAdmin,
|
||||
isAuthenticated: !!accessToken,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
login,
|
||||
logout,
|
||||
refreshAccessToken,
|
||||
setUserData,
|
||||
userData,
|
||||
getAuthentication,
|
||||
authenticationErrorCount: 0,
|
||||
setAutoLogin,
|
||||
autoLogin,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { createContext, useEffect, useState } from "react";
|
||||
import { getRepoStars } from "../controllers/API";
|
||||
import { darkContextType } from "../types/typesContext";
|
||||
|
||||
const initialValue = {
|
||||
dark: {},
|
||||
setDark: () => {},
|
||||
stars: 0,
|
||||
setStars: (stars) => 0,
|
||||
};
|
||||
|
||||
export const darkContext = createContext<darkContextType>(initialValue);
|
||||
|
|
@ -12,6 +15,16 @@ export function DarkProvider({ children }) {
|
|||
const [dark, setDark] = useState(
|
||||
JSON.parse(window.localStorage.getItem("isDark")!) ?? false
|
||||
);
|
||||
const [stars, setStars] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchStars() {
|
||||
const starsCount = await getRepoStars("logspace-ai", "langflow");
|
||||
setStars(starsCount);
|
||||
}
|
||||
fetchStars();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (dark) {
|
||||
document.getElementById("body")!.classList.add("dark");
|
||||
|
|
@ -20,9 +33,12 @@ export function DarkProvider({ children }) {
|
|||
}
|
||||
window.localStorage.setItem("isDark", dark.toString());
|
||||
}, [dark]);
|
||||
|
||||
return (
|
||||
<darkContext.Provider
|
||||
value={{
|
||||
setStars,
|
||||
stars,
|
||||
dark,
|
||||
setDark,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import { ReactNode } from "react";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { ReactFlowProvider } from "reactflow";
|
||||
import { TooltipProvider } from "../components/ui/tooltip";
|
||||
import { ApiInterceptor } from "../controllers/API/api";
|
||||
import { SSEProvider } from "./SSEContext";
|
||||
import { AlertProvider } from "./alertContext";
|
||||
import { AuthProvider } from "./authContext";
|
||||
import { DarkProvider } from "./darkContext";
|
||||
import { LocationProvider } from "./locationContext";
|
||||
import { TabsProvider } from "./tabsContext";
|
||||
|
|
@ -13,23 +16,28 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
|
|||
//element to wrap all context
|
||||
return (
|
||||
<>
|
||||
<TooltipProvider>
|
||||
<ReactFlowProvider>
|
||||
<DarkProvider>
|
||||
<AlertProvider>
|
||||
<TypesProvider>
|
||||
<LocationProvider>
|
||||
<SSEProvider>
|
||||
<TabsProvider>
|
||||
<UndoRedoProvider>{children}</UndoRedoProvider>
|
||||
</TabsProvider>
|
||||
</SSEProvider>
|
||||
</LocationProvider>
|
||||
</TypesProvider>
|
||||
</AlertProvider>
|
||||
</DarkProvider>
|
||||
</ReactFlowProvider>
|
||||
</TooltipProvider>
|
||||
<BrowserRouter>
|
||||
<AlertProvider>
|
||||
<AuthProvider>
|
||||
<TooltipProvider>
|
||||
<ReactFlowProvider>
|
||||
<DarkProvider>
|
||||
<TypesProvider>
|
||||
<LocationProvider>
|
||||
<ApiInterceptor />
|
||||
<SSEProvider>
|
||||
<TabsProvider>
|
||||
<UndoRedoProvider>{children}</UndoRedoProvider>
|
||||
</TabsProvider>
|
||||
</SSEProvider>
|
||||
</LocationProvider>
|
||||
</TypesProvider>
|
||||
</DarkProvider>
|
||||
</ReactFlowProvider>
|
||||
</TooltipProvider>
|
||||
</AuthProvider>
|
||||
</AlertProvider>
|
||||
</BrowserRouter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { AxiosError } from "axios";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
ReactNode,
|
||||
|
|
@ -21,7 +22,7 @@ import {
|
|||
import { APIClassType, APITemplateType } from "../types/api";
|
||||
import { tweakType } from "../types/components";
|
||||
import { FlowType, NodeDataType, NodeType } from "../types/flow";
|
||||
import { TabsContextType, TabsState, errorsVarType } from "../types/tabs";
|
||||
import { TabsContextType, TabsState } from "../types/tabs";
|
||||
import {
|
||||
addVersionToDuplicates,
|
||||
updateIds,
|
||||
|
|
@ -29,6 +30,7 @@ import {
|
|||
} from "../utils/reactflowUtils";
|
||||
import { getRandomDescription, getRandomName } from "../utils/utils";
|
||||
import { alertContext } from "./alertContext";
|
||||
import { AuthContext } from "./authContext";
|
||||
import { typesContext } from "./typesContext";
|
||||
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
|
|
@ -68,7 +70,9 @@ export const TabsContext = createContext<TabsContextType>(
|
|||
);
|
||||
|
||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||
const { setErrorData, setNoticeData, setSuccessData } =
|
||||
useContext(alertContext);
|
||||
const { getAuthentication } = useContext(AuthContext);
|
||||
|
||||
const [tabId, setTabId] = useState("");
|
||||
|
||||
|
|
@ -117,24 +121,26 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
try {
|
||||
processDBData(DbData);
|
||||
updateStateWithDbData(DbData);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// get data from db
|
||||
//get tabs locally saved
|
||||
// let tabsData = getLocalStorageTabsData();
|
||||
refreshFlows();
|
||||
}, [templates]);
|
||||
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
|
||||
if (getAuthentication() === true) {
|
||||
// get data from db
|
||||
//get tabs locally saved
|
||||
// let tabsData = getLocalStorageTabsData();
|
||||
refreshFlows();
|
||||
}
|
||||
}, [templates, getAuthentication()]);
|
||||
|
||||
function getTabsDataFromDB() {
|
||||
//get tabs from db
|
||||
return readFlowsFromDatabase();
|
||||
}
|
||||
|
||||
function processDBData(DbData: FlowType[]) {
|
||||
DbData.forEach((flow: FlowType) => {
|
||||
try {
|
||||
|
|
@ -143,9 +149,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
processFlowEdges(flow);
|
||||
processFlowNodes(flow);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -478,7 +482,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
return id;
|
||||
} catch (error) {
|
||||
// Handle the error if needed
|
||||
console.error("Error while adding flow:", error);
|
||||
throw error; // Re-throw the error so the caller can handle it if needed
|
||||
}
|
||||
} else {
|
||||
|
|
@ -579,6 +582,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
const updatedFlow = await updateFlowInDatabase(newFlow);
|
||||
if (updatedFlow) {
|
||||
// updates flow in state
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||
|
|
@ -601,7 +605,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}
|
||||
} catch (err) {
|
||||
setErrorData(err as errorsVarType);
|
||||
setErrorData({
|
||||
title: "Error while saving changes",
|
||||
list: [(err as AxiosError).message],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { getAll, getHealth } from "../controllers/API";
|
|||
import { APIKindType } from "../types/api";
|
||||
import { typesContextType } from "../types/typesContext";
|
||||
import { alertContext } from "./alertContext";
|
||||
import { AuthContext } from "./authContext";
|
||||
|
||||
//context to share types adn functions from nodes to flow
|
||||
|
||||
|
|
@ -37,55 +38,56 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
|||
const [data, setData] = useState({});
|
||||
const [fetchError, setFetchError] = useState(false);
|
||||
const { setLoading } = useContext(alertContext);
|
||||
const { getAuthentication } = useContext(AuthContext);
|
||||
|
||||
useEffect(() => {
|
||||
// If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks.
|
||||
if (getAuthentication() === true) {
|
||||
getTypes();
|
||||
}
|
||||
}, [getAuthentication()]);
|
||||
|
||||
async function getTypes(): Promise<void> {
|
||||
// We will keep a flag to handle the case where the component is unmounted before the API call resolves.
|
||||
let isMounted = true;
|
||||
|
||||
async function getTypes(): Promise<void> {
|
||||
try {
|
||||
const result = await getAll();
|
||||
// Make sure to only update the state if the component is still mounted.
|
||||
if (isMounted && result?.status === 200) {
|
||||
setLoading(false);
|
||||
setData(result.data);
|
||||
setTemplates(
|
||||
Object.keys(result.data).reduce((acc, curr) => {
|
||||
try {
|
||||
const result = await getAll();
|
||||
// Make sure to only update the state if the component is still mounted.
|
||||
if (isMounted && result?.status === 200) {
|
||||
setLoading(false);
|
||||
setData(result.data);
|
||||
setTemplates(
|
||||
Object.keys(result.data).reduce((acc, curr) => {
|
||||
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
|
||||
acc[c] = result.data[curr][c];
|
||||
});
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||
setTypes(
|
||||
// Reverse the keys so the tool world does not overlap
|
||||
Object.keys(result.data)
|
||||
.reverse()
|
||||
.reduce((acc, curr) => {
|
||||
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
|
||||
acc[c] = result.data[curr][c];
|
||||
acc[c] = curr;
|
||||
// Add the base classes to the accumulator as well.
|
||||
result.data[curr][c].base_classes?.forEach((b) => {
|
||||
acc[b] = curr;
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||
setTypes(
|
||||
// Reverse the keys so the tool world does not overlap
|
||||
Object.keys(result.data)
|
||||
.reverse()
|
||||
.reduce((acc, curr) => {
|
||||
Object.keys(result.data[curr]).forEach(
|
||||
(c: keyof APIKindType) => {
|
||||
acc[c] = curr;
|
||||
// Add the base classes to the accumulator as well.
|
||||
result.data[curr][c].base_classes?.forEach((b) => {
|
||||
acc[b] = curr;
|
||||
});
|
||||
}
|
||||
);
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("An error has occurred while fetching types.");
|
||||
await getHealth().catch((e) => {
|
||||
setFetchError(true);
|
||||
});
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("An error has occurred while fetching types.");
|
||||
await getHealth().catch((e) => {
|
||||
setFetchError(true);
|
||||
});
|
||||
}
|
||||
|
||||
getTypes();
|
||||
}, []);
|
||||
}
|
||||
|
||||
function deleteNode(idx: string) {
|
||||
reactFlowInstance!.setNodes(
|
||||
|
|
|
|||
|
|
@ -1,60 +1,117 @@
|
|||
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import { useContext, useEffect } from "react";
|
||||
import { Cookies } from "react-cookie";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { renewAccessToken } from ".";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
// Create a new Axios instance
|
||||
const api: AxiosInstance = axios.create({
|
||||
baseURL: "",
|
||||
});
|
||||
|
||||
function ApiInterceptor(): null {
|
||||
const retryCounts = useRef([]);
|
||||
function ApiInterceptor() {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
let { accessToken, login, logout, authenticationErrorCount } =
|
||||
useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
const cookies = new Cookies();
|
||||
|
||||
useEffect(() => {
|
||||
const interceptor = api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// let retryCount = 0;
|
||||
// while (retryCount < 4) {
|
||||
// await sleep(5000); // Sleep for 5 seconds
|
||||
// retryCount++;
|
||||
// try {
|
||||
// const response = await axios.request(error.config);
|
||||
// return response;
|
||||
// } catch (error) {
|
||||
// if (retryCount === 3) {
|
||||
// setErrorData({
|
||||
// title: "There was an error on web connection, please: ",
|
||||
// list: [
|
||||
// "Refresh the page",
|
||||
// "Use a new flow tab",
|
||||
// "Check if the backend is up",
|
||||
// "Endpoint: " + error.config?.url,
|
||||
// ],
|
||||
// });
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if (error.response?.status === 401) {
|
||||
const refreshToken = cookies.get("refresh_token");
|
||||
if (refreshToken && refreshToken !== "auto") {
|
||||
authenticationErrorCount = authenticationErrorCount + 1;
|
||||
if (authenticationErrorCount > 3) {
|
||||
authenticationErrorCount = 0;
|
||||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
|
||||
const res = await renewAccessToken(refreshToken);
|
||||
login(res.data.access_token, res.data.refresh_token);
|
||||
try {
|
||||
if (error?.config?.headers) {
|
||||
delete error.config.headers["Authorization"];
|
||||
error.config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||
const response = await axios.request(error.config);
|
||||
return response;
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!refreshToken && error?.config?.url?.includes("login")) {
|
||||
return Promise.reject(error);
|
||||
} else {
|
||||
logout();
|
||||
navigate("/login");
|
||||
}
|
||||
} else {
|
||||
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
||||
return Promise.reject(error);
|
||||
// }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const isAuthorizedURL = (url) => {
|
||||
const authorizedDomains = [
|
||||
"https://raw.githubusercontent.com/logspace-ai/langflow_examples/main/examples",
|
||||
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples",
|
||||
"https://api.github.com/repos/logspace-ai/langflow",
|
||||
"auto_login",
|
||||
];
|
||||
|
||||
const authorizedEndpoints = ["auto_login"];
|
||||
|
||||
try {
|
||||
const parsedURL = new URL(url);
|
||||
|
||||
const isDomainAllowed = authorizedDomains.some(
|
||||
(domain) => parsedURL.origin === new URL(domain).origin
|
||||
);
|
||||
const isEndpointAllowed = authorizedEndpoints.some((endpoint) =>
|
||||
parsedURL.pathname.includes(endpoint)
|
||||
);
|
||||
|
||||
return isDomainAllowed || isEndpointAllowed;
|
||||
} catch (e) {
|
||||
// Invalid URL
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Request interceptor to add access token to every request
|
||||
const requestInterceptor = api.interceptors.request.use(
|
||||
(config) => {
|
||||
if (accessToken && !isAuthorizedURL(config?.url)) {
|
||||
config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
// Clean up the interceptor when the component unmounts
|
||||
// Clean up the interceptors when the component unmounts
|
||||
api.interceptors.response.eject(interceptor);
|
||||
api.interceptors.request.eject(requestInterceptor);
|
||||
};
|
||||
}, [retryCounts]);
|
||||
}, [accessToken, setErrorData]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Function to sleep for a given duration in milliseconds
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export { ApiInterceptor, api };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import { AxiosResponse } from "axios";
|
||||
import { ReactFlowJsonObject } from "reactflow";
|
||||
import { BASE_URL_API } from "../../constants/constants";
|
||||
import { api } from "../../controllers/API/api";
|
||||
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
||||
import {
|
||||
APIObjectType,
|
||||
LoginType,
|
||||
Users,
|
||||
sendAllProps,
|
||||
} from "../../types/api/index";
|
||||
import { UserInputType } from "../../types/components";
|
||||
import { FlowStyleType, FlowType } from "../../types/flow";
|
||||
import {
|
||||
APIClassType,
|
||||
|
|
@ -18,7 +25,7 @@ import {
|
|||
* @returns {Promise<AxiosResponse<APIObjectType>>} A promise that resolves to an AxiosResponse containing all the objects.
|
||||
*/
|
||||
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
||||
return await api.get(`/api/v1/all`);
|
||||
return await api.get(`${BASE_URL_API}all`);
|
||||
}
|
||||
|
||||
const GITHUB_API_URL = "https://api.github.com";
|
||||
|
|
@ -40,13 +47,13 @@ export async function getRepoStars(owner: string, repo: string) {
|
|||
* @returns {AxiosResponse<any>} The API response.
|
||||
*/
|
||||
export async function sendAll(data: sendAllProps) {
|
||||
return await api.post(`/api/v1/predict`, data);
|
||||
return await api.post(`${BASE_URL_API}predict`, data);
|
||||
}
|
||||
|
||||
export async function postValidateCode(
|
||||
code: string
|
||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||
return await api.post("/api/v1/validate/code", { code });
|
||||
return await api.post(`${BASE_URL_API}validate/code`, { code });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -61,7 +68,7 @@ export async function postValidatePrompt(
|
|||
template: string,
|
||||
frontend_node: APIClassType
|
||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||
return await api.post("/api/v1/validate/prompt", {
|
||||
return await api.post(`${BASE_URL_API}validate/prompt`, {
|
||||
name: name,
|
||||
template: template,
|
||||
frontend_node: frontend_node,
|
||||
|
|
@ -105,7 +112,7 @@ export async function saveFlowToDatabase(newFlow: {
|
|||
style?: FlowStyleType;
|
||||
}): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.post("/api/v1/flows/", {
|
||||
const response = await api.post(`${BASE_URL_API}flows/`, {
|
||||
name: newFlow.name,
|
||||
data: newFlow.data,
|
||||
description: newFlow.description,
|
||||
|
|
@ -131,7 +138,7 @@ export async function updateFlowInDatabase(
|
|||
updatedFlow: FlowType
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(`/api/v1/flows/${updatedFlow.id}`, {
|
||||
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
|
||||
name: updatedFlow.name,
|
||||
data: updatedFlow.data,
|
||||
description: updatedFlow.description,
|
||||
|
|
@ -155,8 +162,8 @@ export async function updateFlowInDatabase(
|
|||
*/
|
||||
export async function readFlowsFromDatabase() {
|
||||
try {
|
||||
const response = await api.get("/api/v1/flows/");
|
||||
if (response.status !== 200) {
|
||||
const response = await api.get(`${BASE_URL_API}flows/`);
|
||||
if (response?.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.data;
|
||||
|
|
@ -168,8 +175,8 @@ export async function readFlowsFromDatabase() {
|
|||
|
||||
export async function downloadFlowsFromDatabase() {
|
||||
try {
|
||||
const response = await api.get("/api/v1/flows/download/");
|
||||
if (response.status !== 200) {
|
||||
const response = await api.get(`${BASE_URL_API}flows/download/`);
|
||||
if (response?.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.data;
|
||||
|
|
@ -181,7 +188,7 @@ export async function downloadFlowsFromDatabase() {
|
|||
|
||||
export async function uploadFlowsToDatabase(flows: FormData) {
|
||||
try {
|
||||
const response = await api.post(`/api/v1/flows/upload/`, flows);
|
||||
const response = await api.post(`${BASE_URL_API}flows/upload/`, flows);
|
||||
|
||||
if (response.status !== 201) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
|
|
@ -202,7 +209,7 @@ export async function uploadFlowsToDatabase(flows: FormData) {
|
|||
*/
|
||||
export async function deleteFlowFromDatabase(flowId: string) {
|
||||
try {
|
||||
const response = await api.delete(`/api/v1/flows/${flowId}`);
|
||||
const response = await api.delete(`${BASE_URL_API}flows/${flowId}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -222,7 +229,7 @@ export async function deleteFlowFromDatabase(flowId: string) {
|
|||
*/
|
||||
export async function getFlowFromDatabase(flowId: number) {
|
||||
try {
|
||||
const response = await api.get(`/api/v1/flows/${flowId}`);
|
||||
const response = await api.get(`${BASE_URL_API}flows/${flowId}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -241,7 +248,7 @@ export async function getFlowFromDatabase(flowId: number) {
|
|||
*/
|
||||
export async function getFlowStylesFromDatabase() {
|
||||
try {
|
||||
const response = await api.get("/api/v1/flow_styles/");
|
||||
const response = await api.get(`${BASE_URL_API}flow_styles/`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -261,7 +268,7 @@ export async function getFlowStylesFromDatabase() {
|
|||
*/
|
||||
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
||||
try {
|
||||
const response = await api.post("/api/v1/flow_styles/", flowStyle, {
|
||||
const response = await api.post(`${BASE_URL_API}flow_styles/`, flowStyle, {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -284,7 +291,7 @@ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
|||
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the version information.
|
||||
*/
|
||||
export async function getVersion() {
|
||||
const respnose = await api.get("/api/v1/version");
|
||||
const respnose = await api.get(`${BASE_URL_API}version`);
|
||||
return respnose.data;
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +313,7 @@ export async function getHealth() {
|
|||
export async function getBuildStatus(
|
||||
flowId: string
|
||||
): Promise<BuildStatusTypeAPI> {
|
||||
return await api.get(`/api/v1/build/${flowId}/status`);
|
||||
return await api.get(`${BASE_URL_API}build/${flowId}/status`);
|
||||
}
|
||||
|
||||
//docs for postbuildinit
|
||||
|
|
@ -319,7 +326,7 @@ export async function getBuildStatus(
|
|||
export async function postBuildInit(
|
||||
flow: FlowType
|
||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||
return await api.post(`/api/v1/build/init/${flow.id}`, flow);
|
||||
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
|
||||
}
|
||||
|
||||
// fetch(`/upload/${id}`, {
|
||||
|
|
@ -337,12 +344,160 @@ export async function uploadFile(
|
|||
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return await api.post(`/api/v1/upload/${id}`, formData);
|
||||
return await api.post(`${BASE_URL_API}upload/${id}`, formData);
|
||||
}
|
||||
|
||||
export async function postCustomComponent(
|
||||
code: string,
|
||||
apiClass: APIClassType
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
return await api.post(`/api/v1/custom_component`, { code });
|
||||
return await api.post(`${BASE_URL_API}custom_component`, { code });
|
||||
}
|
||||
|
||||
export async function onLogin(user: LoginType) {
|
||||
try {
|
||||
const response = await api.post(
|
||||
`${BASE_URL_API}login`,
|
||||
new URLSearchParams({
|
||||
username: user.username,
|
||||
password: user.password,
|
||||
}).toString(),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.data;
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function autoLogin() {
|
||||
try {
|
||||
const response = await api.get(`${BASE_URL_API}auto_login`);
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.data;
|
||||
return data;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function renewAccessToken(token: string) {
|
||||
try {
|
||||
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLoggedUser(): Promise<Users> {
|
||||
try {
|
||||
const res = await api.get(`${BASE_URL_API}user`);
|
||||
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function addUser(user: UserInputType): Promise<Users> {
|
||||
try {
|
||||
const res = await api.post(`${BASE_URL_API}user`, user);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUsersPage(
|
||||
skip: number,
|
||||
limit: number
|
||||
): Promise<[Users]> {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`${BASE_URL_API}users?skip=${skip}&limit=${limit}`
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteUser(user_id: string) {
|
||||
try {
|
||||
const res = await api.delete(`${BASE_URL_API}user/${user_id}`);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateUser(user_id: string, user: Users) {
|
||||
try {
|
||||
const res = await api.patch(`${BASE_URL_API}user/${user_id}`, user);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getApiKey() {
|
||||
try {
|
||||
const res = await api.get(`${BASE_URL_API}api_key`);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createApiKey(name: string) {
|
||||
try {
|
||||
const res = await api.post(`${BASE_URL_API}api_key`, { name });
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteApiKey(api_key: string) {
|
||||
try {
|
||||
const res = await api.delete(`${BASE_URL_API}api_key/${api_key}`);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import ContextWrapper from "./contexts";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
|
||||
import { ApiInterceptor } from "./controllers/API/api";
|
||||
// @ts-ignore
|
||||
import "./style/index.css";
|
||||
// @ts-ignore
|
||||
|
|
@ -17,10 +15,7 @@ const root = ReactDOM.createRoot(
|
|||
);
|
||||
root.render(
|
||||
<ContextWrapper>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<ApiInterceptor />
|
||||
</BrowserRouter>
|
||||
<App />
|
||||
</ContextWrapper>
|
||||
);
|
||||
reportWebVitals();
|
||||
|
|
|
|||
202
src/frontend/src/modals/SecretKeyModal/index.tsx
Normal file
202
src/frontend/src/modals/SecretKeyModal/index.tsx
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import { CONTROL_NEW_API_KEY } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { createApiKey } from "../../controllers/API";
|
||||
import {
|
||||
ApiKeyInputType,
|
||||
ApiKeyType,
|
||||
inputHandlerEventType,
|
||||
} from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
export default function SecretKeyModal({
|
||||
title,
|
||||
cancelText,
|
||||
confirmationText,
|
||||
children,
|
||||
icon,
|
||||
data,
|
||||
onCloseModal,
|
||||
}: ApiKeyType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
const [open, setOpen] = useState(false);
|
||||
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
|
||||
const [apiKeyValue, setApiKeyValue] = useState("");
|
||||
const [inputState, setInputState] =
|
||||
useState<ApiKeyInputType>(CONTROL_NEW_API_KEY);
|
||||
const [renderKey, setRenderKey] = useState(false);
|
||||
const [textCopied, setTextCopied] = useState(true);
|
||||
const { setSuccessData } = useContext(alertContext);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setRenderKey(false);
|
||||
resetForm();
|
||||
} else {
|
||||
onCloseModal();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
function resetForm() {
|
||||
setApiKeyName("");
|
||||
setApiKeyValue("");
|
||||
}
|
||||
|
||||
const handleCopyClick = async () => {
|
||||
if (apiKeyValue) {
|
||||
await navigator.clipboard.writeText(apiKeyValue);
|
||||
inputRef?.current?.focus();
|
||||
inputRef?.current?.select();
|
||||
setSuccessData({
|
||||
title: "API Key copied!",
|
||||
});
|
||||
setTextCopied(false);
|
||||
|
||||
setTimeout(() => {
|
||||
setTextCopied(true);
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
function handleAddNewKey() {
|
||||
createApiKey(apiKeyName)
|
||||
.then((res) => {
|
||||
setApiKeyValue(res["api_key"]);
|
||||
})
|
||||
.catch((err) => {});
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseModal size="small-h-full" open={open} setOpen={setOpen}>
|
||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={""}>
|
||||
<span className="pr-2">{title}</span>
|
||||
<Icon
|
||||
name="icon"
|
||||
className="h-6 w-6 pl-1 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
{renderKey === true && (
|
||||
<>
|
||||
<span className="text-xs">
|
||||
Please save this secret key somewhere safe and accessible. For
|
||||
security reasons,{" "}
|
||||
<strong>you won't be able to view it again</strong> through your
|
||||
account. If you lose this secret key, you'll need to generate a
|
||||
new one.
|
||||
</span>
|
||||
<div className="flex pt-3">
|
||||
<div className="w-full">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
onChange={(event) => {
|
||||
setApiKeyValue(event.target.value);
|
||||
}}
|
||||
readOnly={true}
|
||||
value={apiKeyValue}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button
|
||||
className="ml-3"
|
||||
onClick={() => {
|
||||
handleCopyClick();
|
||||
}}
|
||||
>
|
||||
{textCopied ? (
|
||||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
) : (
|
||||
<IconComponent name="Check" className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
setRenderKey(true);
|
||||
handleAddNewKey();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
{renderKey === false && (
|
||||
<div className="grid gap-5">
|
||||
<Form.Field name="username">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Name (optional){" "}
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "apikeyname", value } });
|
||||
setApiKeyName(value);
|
||||
}}
|
||||
value={apiKeyName}
|
||||
className="primary-input"
|
||||
placeholder="My key name"
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
</div>
|
||||
)}
|
||||
{renderKey === false && (
|
||||
<div className="float-right">
|
||||
<Button
|
||||
className="mr-3"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
|
||||
<Form.Submit asChild>
|
||||
<Button className="mt-8">{confirmationText}</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{renderKey === true && (
|
||||
<div className="float-right">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setRenderKey(false);
|
||||
}}
|
||||
className="mt-8"
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Form.Root>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +1,15 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { UserManagementType } from "../../types/components";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { CONTROL_NEW_USER } from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import {
|
||||
UserInputType,
|
||||
UserManagementType,
|
||||
inputHandlerEventType,
|
||||
} from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
|
|
@ -18,18 +25,32 @@ export default function UserManagementModal({
|
|||
onConfirm,
|
||||
}: UserManagementType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
const [confirmPwdVisible, setConfirmPwdVisible] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [password, setPassword] = useState(data?.password ?? "");
|
||||
const [username, setUserName] = useState(data?.user ?? "");
|
||||
const [username, setUserName] = useState(data?.username ?? "");
|
||||
const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
|
||||
const [isActive, setIsActive] = useState(data?.is_active ?? false);
|
||||
const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false);
|
||||
const [inputState, setInputState] = useState<UserInputType>(CONTROL_NEW_USER);
|
||||
const { userData } = useContext(AuthContext);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
resetForm();
|
||||
} else {
|
||||
handleInput({ target: { name: "username", value: username } });
|
||||
handleInput({ target: { name: "is_active", value: isActive } });
|
||||
handleInput({ target: { name: "is_superuser", value: isSuperUser } });
|
||||
}
|
||||
}, [data, open]);
|
||||
}, [open]);
|
||||
|
||||
function resetForm() {
|
||||
setPassword("");
|
||||
|
|
@ -55,10 +76,8 @@ export default function UserManagementModal({
|
|||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = Object.fromEntries(new FormData(event.currentTarget));
|
||||
resetForm();
|
||||
onConfirm(index ?? -1, data);
|
||||
onConfirm(1, inputState);
|
||||
setOpen(false);
|
||||
event.preventDefault();
|
||||
}}
|
||||
|
|
@ -79,8 +98,9 @@ export default function UserManagementModal({
|
|||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setUserName(input.target.value);
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "username", value } });
|
||||
setUserName(value);
|
||||
}}
|
||||
value={username}
|
||||
className="primary-input"
|
||||
|
|
@ -106,22 +126,40 @@ export default function UserManagementModal({
|
|||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
<Form.Label className="data-[invalid]:label-invalid flex">
|
||||
Password{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
<span className="ml-1 mr-1 font-medium text-destructive">
|
||||
*
|
||||
</span>
|
||||
{pwdVisible && (
|
||||
<Eye
|
||||
onClick={() => setPwdVisible(!pwdVisible)}
|
||||
className="h-5 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
)}
|
||||
{!pwdVisible && (
|
||||
<EyeOff
|
||||
onClick={() => setPwdVisible(!pwdVisible)}
|
||||
className="h-5 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
)}
|
||||
</Form.Label>
|
||||
</div>
|
||||
<InputComponent
|
||||
onChange={(input) => {
|
||||
setPassword(input);
|
||||
}}
|
||||
value={password}
|
||||
password={true}
|
||||
isForm
|
||||
className="primary-input"
|
||||
required
|
||||
placeholder="Password"
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
setPassword(value);
|
||||
}}
|
||||
value={password}
|
||||
className="primary-input"
|
||||
required={data ? false : true}
|
||||
type={pwdVisible ? "text" : "password"}
|
||||
/>
|
||||
</Form.Control>
|
||||
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter a password
|
||||
</Form.Message>
|
||||
|
|
@ -146,93 +184,108 @@ export default function UserManagementModal({
|
|||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
<Form.Label className="data-[invalid]:label-invalid flex">
|
||||
Confirm password{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
<span className="ml-1 mr-1 font-medium text-destructive">
|
||||
*
|
||||
</span>
|
||||
{confirmPwdVisible && (
|
||||
<Eye
|
||||
onClick={() =>
|
||||
setConfirmPwdVisible(!confirmPwdVisible)
|
||||
}
|
||||
className="h-5 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
)}
|
||||
{!confirmPwdVisible && (
|
||||
<EyeOff
|
||||
onClick={() =>
|
||||
setConfirmPwdVisible(!confirmPwdVisible)
|
||||
}
|
||||
className="h-5 cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
)}
|
||||
</Form.Label>
|
||||
</div>
|
||||
<InputComponent
|
||||
onChange={(input) => {
|
||||
setConfirmPassword(input);
|
||||
}}
|
||||
value={confirmPassword}
|
||||
password={true}
|
||||
isForm
|
||||
className="primary-input"
|
||||
required
|
||||
placeholder="Confirm your password"
|
||||
/>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setConfirmPassword(input.target.value);
|
||||
}}
|
||||
value={confirmPassword}
|
||||
className="primary-input"
|
||||
required={data ? false : true}
|
||||
type={confirmPwdVisible ? "text" : "password"}
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please confirm your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
<Form.Field name="email">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Email <span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter your email
|
||||
</Form.Message>
|
||||
<Form.Message className="field-invalid" match="typeMismatch">
|
||||
Please provide a valid email
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input className="primary-input" type="email" required />
|
||||
</Form.Control>
|
||||
</Form.Field> */}
|
||||
|
||||
{/*
|
||||
<Form.Field name="birth">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Date of birth{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter your date of birth
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
type="date"
|
||||
className="primary-input"
|
||||
required
|
||||
max={new Date().toISOString().split("T")[0]}
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Field> */}
|
||||
<div className="flex gap-8">
|
||||
<Form.Field name="is_active">
|
||||
<div>
|
||||
<Form.Label className="data-[invalid]:label-invalid mr-3">
|
||||
Active
|
||||
</Form.Label>
|
||||
<Form.Control asChild>
|
||||
<Checkbox
|
||||
value={isActive}
|
||||
checked={isActive}
|
||||
id="is_active"
|
||||
className="relative top-0.5"
|
||||
onCheckedChange={(value) => {
|
||||
handleInput({ target: { name: "is_active", value } });
|
||||
setIsActive(value);
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
</div>
|
||||
</Form.Field>
|
||||
{userData?.is_superuser && (
|
||||
<Form.Field name="is_superuser">
|
||||
<div>
|
||||
<Form.Label className="data-[invalid]:label-invalid mr-3">
|
||||
Superuser
|
||||
</Form.Label>
|
||||
<Form.Control asChild>
|
||||
<Checkbox
|
||||
checked={isSuperUser}
|
||||
value={isSuperUser}
|
||||
id="is_superuser"
|
||||
className="relative top-0.5"
|
||||
onCheckedChange={(value) => {
|
||||
handleInput({
|
||||
target: { name: "is_superuser", value },
|
||||
});
|
||||
setIsSuperUser(value);
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
</div>
|
||||
</Form.Field>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="float-right">
|
||||
<Form.Submit asChild>
|
||||
<Button className="mr-3 mt-8">{confirmationText}</Button>
|
||||
</Form.Submit>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
className="mr-3"
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
|
||||
<Form.Submit asChild>
|
||||
<Button className="mt-8">{confirmationText}</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
</Form.Root>
|
||||
</BaseModal.Content>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
|||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { FlowSettingsPropsType } from "../../types/components";
|
||||
import BaseModal from "../baseModal";
|
||||
|
|
@ -12,15 +11,14 @@ export default function FlowSettingsModal({
|
|||
open,
|
||||
setOpen,
|
||||
}: FlowSettingsPropsType): JSX.Element {
|
||||
const { setSuccessData } = useContext(alertContext);
|
||||
const { flows, tabId, updateFlow, saveFlow } = useContext(TabsContext);
|
||||
const flow = flows.find((f) => f.id === tabId);
|
||||
useEffect(() => {
|
||||
setName(flow.name);
|
||||
setDescription(flow.description);
|
||||
}, [flow.name, flow.description]);
|
||||
const [name, setName] = useState(flow.name);
|
||||
const [description, setDescription] = useState(flow.description);
|
||||
setName(flow!.name);
|
||||
setDescription(flow!.description);
|
||||
}, [flow!.name, flow!.description]);
|
||||
const [name, setName] = useState(flow!.name);
|
||||
const [description, setDescription] = useState(flow!.description);
|
||||
const [invalidName, setInvalidName] = useState(false);
|
||||
|
||||
function handleClick(): void {
|
||||
|
|
@ -28,7 +26,6 @@ export default function FlowSettingsModal({
|
|||
savedFlow!.name = name;
|
||||
savedFlow!.description = description;
|
||||
saveFlow(savedFlow!);
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
setOpen(false);
|
||||
}
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
} from "../../components/ui/dialog";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { TabsState } from "../../types/tabs";
|
||||
import { validateNodes } from "../../utils/reactflowUtils";
|
||||
|
|
@ -60,6 +61,7 @@ export default function FormModal({
|
|||
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const { accessToken } = useContext(AuthContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
|
|
@ -160,7 +162,7 @@ export default function FormModal({
|
|||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO improve check of user authentication
|
||||
function getWebSocketUrl(
|
||||
chatId: string,
|
||||
isDevelopment: boolean = false
|
||||
|
|
@ -173,7 +175,7 @@ export default function FormModal({
|
|||
|
||||
return `${
|
||||
isDevelopment ? "ws" : webSocketProtocol
|
||||
}://${host}${chatEndpoint}`;
|
||||
}://${host}${chatEndpoint}?token=${accessToken}`;
|
||||
}
|
||||
|
||||
function handleWsMessage(data: any) {
|
||||
|
|
|
|||
|
|
@ -147,7 +147,6 @@ export default function GenericModal({
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setIsEdit(true);
|
||||
return setErrorData({
|
||||
title: "There is something wrong with this prompt, please review it",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,62 @@
|
|||
import { useContext, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { Input } from "../../../components/ui/input";
|
||||
import { CONTROL_LOGIN_STATE } from "../../../constants/constants";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { AuthContext } from "../../../contexts/authContext";
|
||||
import { getLoggedUser, onLogin } from "../../../controllers/API";
|
||||
import { LoginType } from "../../../types/api";
|
||||
import {
|
||||
inputHandlerEventType,
|
||||
loginInputStateType,
|
||||
} from "../../../types/components";
|
||||
|
||||
export default function LoginAdminPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
function loginAdmin() {
|
||||
navigate("/admin/");
|
||||
const [inputState, setInputState] =
|
||||
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
|
||||
const { login, getAuthentication, setUserData } = useContext(AuthContext);
|
||||
|
||||
const { password, username } = inputState;
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
function signIn() {
|
||||
const user: LoginType = {
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
onLogin(user)
|
||||
.then((user) => {
|
||||
login(user.access_token, user.refresh_token);
|
||||
getUser();
|
||||
navigate("/admin/");
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error signing in",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getUser() {
|
||||
if (getAuthentication) {
|
||||
setTimeout(() => {
|
||||
getLoggedUser()
|
||||
.then((user) => {
|
||||
setUserData(user);
|
||||
})
|
||||
.catch((error) => {});
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -14,11 +64,24 @@ export default function LoginAdminPage() {
|
|||
<div className="flex w-72 flex-col items-center justify-center gap-2">
|
||||
<span className="mb-4 text-5xl">⛓️</span>
|
||||
<span className="mb-6 text-2xl font-semibold text-primary">Admin</span>
|
||||
<Input className="bg-background" placeholder="Email address" />
|
||||
<Input className="bg-background" placeholder="Password" />
|
||||
<Input
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "username", value } });
|
||||
}}
|
||||
className="bg-background"
|
||||
placeholder="Username"
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
onChange={({ target: { value } }) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
}}
|
||||
className="bg-background"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
loginAdmin();
|
||||
signIn();
|
||||
}}
|
||||
variant="default"
|
||||
className="w-full"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import _ from "lodash";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { X } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import PaginatorComponent from "../../components/PaginatorComponent";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import {
|
||||
Table,
|
||||
|
|
@ -14,265 +15,202 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import {
|
||||
addUser,
|
||||
deleteUser,
|
||||
getUsersPage,
|
||||
updateUser,
|
||||
} from "../../controllers/API";
|
||||
import ConfirmationModal from "../../modals/ConfirmationModal";
|
||||
import UserManagementModal from "../../modals/UserManagementModal";
|
||||
import { UserInputType } from "../../types/components";
|
||||
import Header from "../../components/headerComponent";
|
||||
import { Users } from "../../types/api";
|
||||
|
||||
export default function AdminPage() {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const [size, setPageSize] = useState(10);
|
||||
const [index, setPageIndex] = useState(1);
|
||||
const [index, setPageIndex] = useState(0);
|
||||
const [loadingUsers, setLoadingUsers] = useState(true);
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const { userData } = useContext(AuthContext);
|
||||
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
||||
|
||||
const userList = useRef([
|
||||
{
|
||||
user: generateRandomString(50),
|
||||
email: generateRandomString(50) + "@example.com",
|
||||
password: generateRandomString(50),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
]);
|
||||
const userList = useRef([]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
getUsers();
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
const [filterUserList, setFilterUserList] = useState(userList.current);
|
||||
|
||||
function generateRandomString(length) {
|
||||
const characters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
result += characters.charAt(randomIndex);
|
||||
}
|
||||
return result;
|
||||
function getUsers() {
|
||||
setLoadingUsers(true);
|
||||
getUsersPage(index, size)
|
||||
.then((users) => {
|
||||
setTotalRowsCount(users["total_count"]);
|
||||
userList.current = users["users"];
|
||||
setFilterUserList(users["users"]);
|
||||
setLoadingUsers(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoadingUsers(false);
|
||||
});
|
||||
}
|
||||
|
||||
function generateRandomDate() {
|
||||
const start = new Date(2010, 0, 1);
|
||||
const end = new Date();
|
||||
const randomTimestamp =
|
||||
start.getTime() + Math.random() * (end.getTime() - start.getTime());
|
||||
const randomDate = new Date(randomTimestamp);
|
||||
|
||||
const options = { year: "numeric", month: "short", day: "numeric" };
|
||||
return randomDate.toLocaleDateString("en-US");
|
||||
}
|
||||
|
||||
const [editUser, setEditUser] = useState(-1);
|
||||
const [editedUser, setEditedUser] = useState("");
|
||||
const [modalEditOpen, setModalEditOpen] = useState(false);
|
||||
const [modalDeleteOpen, setModalDeleteOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
resetFilter();
|
||||
}, []);
|
||||
|
||||
const handleInputChange = (event, index) => {
|
||||
const user = _.cloneDeepWith(userList.current);
|
||||
user[index].password = event.target.value;
|
||||
userList.current = user;
|
||||
|
||||
const userFilter = _.cloneDeepWith(filterUserList);
|
||||
userFilter[index].password = event.target.value;
|
||||
setFilterUserList(userFilter);
|
||||
|
||||
setEditedUser(event.target.value);
|
||||
};
|
||||
|
||||
function handleChangePagination(pageIndex: number, pageSize: number) {
|
||||
setPageIndex(pageIndex);
|
||||
setPageSize(pageSize);
|
||||
|
||||
const startIndex = (pageIndex - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const newList = userList.current.slice(startIndex, endIndex);
|
||||
|
||||
setFilterUserList(newList);
|
||||
setLoadingUsers(true);
|
||||
getUsersPage(pageIndex, pageSize)
|
||||
.then((users) => {
|
||||
setTotalRowsCount(users["total_count"]);
|
||||
userList.current = users["users"];
|
||||
setFilterUserList(users["users"]);
|
||||
setLoadingUsers(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoadingUsers(false);
|
||||
});
|
||||
}
|
||||
|
||||
function resetFilter() {
|
||||
setFilterUserList(userList.current);
|
||||
setPageIndex(1);
|
||||
setPageIndex(0);
|
||||
setPageSize(10);
|
||||
|
||||
const startIndex = (index - 1) * size;
|
||||
const endIndex = index + size - 1;
|
||||
const newList = userList.current.slice(startIndex, endIndex);
|
||||
|
||||
console.log(userList.current);
|
||||
|
||||
setFilterUserList(newList);
|
||||
getUsers();
|
||||
}
|
||||
|
||||
function handleFilterUsers(input: string) {
|
||||
setInputValue(input);
|
||||
|
||||
if (input === "") {
|
||||
resetFilter();
|
||||
setFilterUserList(userList.current);
|
||||
} else {
|
||||
const filteredList = userList.current.filter(
|
||||
(user) =>
|
||||
user.user.toLowerCase().includes(input.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(input.toLowerCase())
|
||||
const filteredList = userList.current.filter((user:Users) =>
|
||||
user.username.toLowerCase().includes(input.toLowerCase())
|
||||
);
|
||||
setFilterUserList(filteredList);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeleteUser(index) {
|
||||
const user = _.cloneDeepWith(userList.current);
|
||||
user.splice(index, 1);
|
||||
userList.current = user;
|
||||
|
||||
const userFilter = _.cloneDeepWith(filterUserList);
|
||||
userFilter.splice(index, 1);
|
||||
setFilterUserList(userFilter);
|
||||
|
||||
resetFilter();
|
||||
function handleDeleteUser(user) {
|
||||
deleteUser(user.id)
|
||||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User deleted!",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on delete user",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleEditUser(index, user) {
|
||||
const newUser = _.cloneDeepWith(userList.current);
|
||||
newUser[index].password = user.password;
|
||||
newUser[index].user = user.username;
|
||||
userList.current = newUser;
|
||||
resetFilter();
|
||||
function handleEditUser(userId, user) {
|
||||
updateUser(userId, user)
|
||||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User edited!",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on edit user",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleNewUser(user) {
|
||||
const newUser = {
|
||||
user: user.username,
|
||||
email: generateRandomString(50) + "@example.com",
|
||||
password: user.password,
|
||||
register_date: generateRandomDate(),
|
||||
};
|
||||
function handleDisableUser(check, userId, user) {
|
||||
const userEdit = cloneDeep(user);
|
||||
userEdit.is_active = !check;
|
||||
|
||||
userList.current.unshift(newUser);
|
||||
console.log(userList.current);
|
||||
updateUser(userId, userEdit)
|
||||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User edited!",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on edit user",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resetFilter();
|
||||
function handleSuperUserEdit(check, userId, user) {
|
||||
const userEdit = cloneDeep(user);
|
||||
userEdit.is_superuser = !check;
|
||||
updateUser(userId, userEdit)
|
||||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User edited!",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on edit user",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleNewUser(user: UserInputType) {
|
||||
addUser(user)
|
||||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! New user added!",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on add new user",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="main-page-panel">
|
||||
<div className="m-auto flex h-full flex-row justify-center">
|
||||
<div className="basis-5/6">
|
||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
Welcome back!
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Here's a list of all users!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2"></div>
|
||||
</div>
|
||||
|
||||
{userList.current.length === 0 && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2>There's no users left :)</h2>
|
||||
<div className="flex flex-col">
|
||||
<Header />
|
||||
{userData && (
|
||||
<div className="main-page-panel">
|
||||
<div className="m-auto flex h-full flex-row justify-center">
|
||||
<div className="basis-5/6">
|
||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
Welcome back!
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Navigate through this section to efficiently oversee all
|
||||
application users. From here, you can seamlessly manage
|
||||
user accounts.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{userList.current.length > 0 && (
|
||||
<div className="flex items-center space-x-2"></div>
|
||||
</div>
|
||||
|
||||
{userList.current.length === 0 && !loadingUsers && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2>There's no users registered :)</h2>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-1 items-center space-x-2">
|
||||
|
|
@ -285,8 +223,8 @@ export default function AdminPage() {
|
|||
{inputValue.length > 0 && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
resetFilter();
|
||||
setInputValue("");
|
||||
setFilterUserList(userList.current);
|
||||
}}
|
||||
variant="ghost"
|
||||
className="h-8 px-2 lg:px-3"
|
||||
|
|
@ -311,90 +249,183 @@ export default function AdminPage() {
|
|||
</UserManagementModal>
|
||||
</div>
|
||||
</div>
|
||||
{loadingUsers && (
|
||||
<div>
|
||||
<strong>Loading...</strong>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted
|
||||
custom-scroll"
|
||||
className={
|
||||
"max-h-[26rem] min-h-[26rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
|
||||
(loadingUsers ? " border-0" : "")
|
||||
}
|
||||
>
|
||||
<Table className="table-fixed bg-muted outline-1 ">
|
||||
<TableHeader>
|
||||
<Table className={"table-fixed bg-muted outline-1"}>
|
||||
<TableHeader
|
||||
className={
|
||||
loadingUsers
|
||||
? "hidden"
|
||||
: "table-fixed bg-muted outline-1"
|
||||
}
|
||||
>
|
||||
<TableRow>
|
||||
<TableHead className="h-10">User</TableHead>
|
||||
<TableHead className="h-10">Password</TableHead>
|
||||
<TableHead className="h-10">Id</TableHead>
|
||||
<TableHead className="h-10">Username</TableHead>
|
||||
<TableHead className="h-10">Active</TableHead>
|
||||
<TableHead className="h-10">Superuser</TableHead>
|
||||
<TableHead className="h-10">Created At</TableHead>
|
||||
<TableHead className="h-10">Updated At</TableHead>
|
||||
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filterUserList.map((user, index) => (
|
||||
<TableRow key={user.user}>
|
||||
<TableCell className="truncate py-2 font-medium">
|
||||
{user.user}
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
{user.password}
|
||||
</TableCell>
|
||||
<TableCell className="flex w-[100px] py-2 text-right">
|
||||
<div className="flex">
|
||||
<UserManagementModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.user}`}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Edit"
|
||||
icon={"UserPlus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleEditUser(index, user);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Edit" side="top">
|
||||
<IconComponent
|
||||
name="Pencil"
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</UserManagementModal>
|
||||
|
||||
{!loadingUsers && (
|
||||
<TableBody>
|
||||
{filterUserList.map((user:UserInputType, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="truncate py-2 font-medium">
|
||||
<ShadTooltip content={user.id}>
|
||||
<span className="cursor-default">
|
||||
{user.id}
|
||||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
<ShadTooltip content={user.username}>
|
||||
<span className="cursor-default">
|
||||
{user.username}
|
||||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
|
||||
<ConfirmationModal
|
||||
title="Delete"
|
||||
titleHeader="Delete User"
|
||||
title="Edit"
|
||||
titleHeader={`${user.username}`}
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you sure you want to delete this user? This action cannot be undone."
|
||||
modalContent="Are you completely confident about the changes you are making to this user?"
|
||||
cancelText="Cancel"
|
||||
confirmationText="Delete"
|
||||
icon={"UserMinus2"}
|
||||
confirmationText="Confirm"
|
||||
icon={"UserCog2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleDeleteUser(index);
|
||||
handleDisableUser(
|
||||
user.is_active,
|
||||
user.id,
|
||||
user
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Delete" side="top">
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="ml-2 h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
<Checkbox
|
||||
id="is_active"
|
||||
checked={user.is_active}
|
||||
/>
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</TableCell>
|
||||
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
|
||||
<ConfirmationModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.username}`}
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you completely confident about the changes you are making to this user?"
|
||||
cancelText="Cancel"
|
||||
confirmationText="Confirm"
|
||||
icon={"UserCog2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleSuperUserEdit(
|
||||
user.is_superuser,
|
||||
user.id,
|
||||
user
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
id="is_superuser"
|
||||
checked={user.is_superuser}
|
||||
/>
|
||||
</ConfirmationModal>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2 ">
|
||||
{
|
||||
new Date(user.create_at!)
|
||||
.toISOString()
|
||||
.split("T")[0]
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
{
|
||||
new Date(user.updated_at!)
|
||||
.toISOString()
|
||||
.split("T")[0]
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell className="flex w-[100px] py-2 text-right">
|
||||
<div className="flex">
|
||||
<UserManagementModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.id}`}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Save"
|
||||
icon={"UserPlus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, editUser) => {
|
||||
handleEditUser(user.id, editUser);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Edit" side="top">
|
||||
<IconComponent
|
||||
name="Pencil"
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</UserManagementModal>
|
||||
|
||||
<ConfirmationModal
|
||||
title="Delete"
|
||||
titleHeader="Delete User"
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you sure you want to delete this user? This action cannot be undone."
|
||||
cancelText="Cancel"
|
||||
confirmationText="Delete"
|
||||
icon={"UserMinus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleDeleteUser(user);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Delete" side="top">
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="ml-2 h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<PaginatorComponent
|
||||
pageIndex={index}
|
||||
pageSize={size}
|
||||
totalRowsCount={filterUserList.length}
|
||||
totalRowsCount={totalRowsCount}
|
||||
paginate={(pageIndex, pageSize) => {
|
||||
handleChangePagination(pageSize, pageIndex);
|
||||
}}
|
||||
></PaginatorComponent>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
283
src/frontend/src/pages/ApiKeysPage/index.tsx
Normal file
283
src/frontend/src/pages/ApiKeysPage/index.tsx
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { deleteApiKey, getApiKey } from "../../controllers/API";
|
||||
import ConfirmationModal from "../../modals/ConfirmationModal";
|
||||
import SecretKeyModal from "../../modals/SecretKeyModal";
|
||||
|
||||
import moment from "moment";
|
||||
import Header from "../../components/headerComponent";
|
||||
import {
|
||||
API_PAGE_PARAGRAPH_1,
|
||||
API_PAGE_PARAGRAPH_2,
|
||||
API_PAGE_USER_KEYS,
|
||||
LAST_USED_SPAN_1,
|
||||
LAST_USED_SPAN_2,
|
||||
} from "../../constants/constants";
|
||||
import { ApiKey } from "../../types/components";
|
||||
|
||||
export default function ApiKeysPage() {
|
||||
const [loadingKeys, setLoadingKeys] = useState(true);
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const { userData } = useContext(AuthContext);
|
||||
const [userId, setUserId] = useState("");
|
||||
const keysList = useRef([]);
|
||||
|
||||
useEffect(() => {
|
||||
getKeys();
|
||||
}, [userData]);
|
||||
|
||||
function getKeys() {
|
||||
setLoadingKeys(true);
|
||||
if (userData) {
|
||||
getApiKey()
|
||||
.then((keys: [ApiKey]) => {
|
||||
keysList.current = keys["api_keys"];
|
||||
setUserId(keys["user_id"]);
|
||||
setLoadingKeys(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoadingKeys(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetFilter() {
|
||||
getKeys();
|
||||
}
|
||||
|
||||
function handleDeleteKey(keys) {
|
||||
deleteApiKey(keys)
|
||||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! Key deleted!",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on delete key",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function lastUsedMessage() {
|
||||
return (
|
||||
<div className="text-xs">
|
||||
<span>
|
||||
{LAST_USED_SPAN_1}
|
||||
<br></br> {LAST_USED_SPAN_2}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header></Header>
|
||||
{userData && (
|
||||
<div className="main-page-panel">
|
||||
<div className="m-auto flex h-full flex-row justify-center">
|
||||
<div className="basis-5/6">
|
||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
API keys
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{API_PAGE_PARAGRAPH_1}
|
||||
<br />
|
||||
{API_PAGE_PARAGRAPH_2}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2"></div>
|
||||
</div>
|
||||
|
||||
{keysList.current &&
|
||||
keysList.current.length === 0 &&
|
||||
!loadingKeys && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2>{API_PAGE_USER_KEYS}</h2>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
{loadingKeys && (
|
||||
<div>
|
||||
<strong>Loading...</strong>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={
|
||||
"max-h-[15rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
|
||||
(loadingKeys ? " border-0" : "")
|
||||
}
|
||||
>
|
||||
{keysList.current &&
|
||||
keysList.current.length > 0 &&
|
||||
!loadingKeys && (
|
||||
<Table className={"table-fixed bg-muted outline-1"}>
|
||||
<TableHeader
|
||||
className={
|
||||
loadingKeys
|
||||
? "hidden"
|
||||
: "table-fixed bg-muted outline-1"
|
||||
}
|
||||
>
|
||||
<TableRow>
|
||||
<TableHead className="h-10">Name</TableHead>
|
||||
<TableHead className="h-10">Key</TableHead>
|
||||
<TableHead className="h-10">Created</TableHead>
|
||||
<TableHead className="flex h-10 items-center">
|
||||
Last Used
|
||||
<ShadTooltip
|
||||
side="top"
|
||||
content={lastUsedMessage()}
|
||||
>
|
||||
<div>
|
||||
<IconComponent
|
||||
name="Info"
|
||||
className="ml-1 h-3 w-3"
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</TableHead>
|
||||
<TableHead className="h-10">Total Uses</TableHead>
|
||||
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{!loadingKeys && (
|
||||
<TableBody>
|
||||
{keysList.current.map(
|
||||
(api_keys: ApiKey, index: number) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="truncate py-2">
|
||||
<ShadTooltip content={api_keys.name}>
|
||||
<span className="cursor-default">
|
||||
{api_keys.name ? api_keys.name : "-"}
|
||||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
<span className="cursor-default">
|
||||
{api_keys.api_key}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2 ">
|
||||
<ShadTooltip
|
||||
side="top"
|
||||
content={moment(
|
||||
api_keys.created_at
|
||||
).format("YYYY-MM-DD HH:mm")}
|
||||
>
|
||||
<div>
|
||||
{moment(api_keys.created_at).format(
|
||||
"YYYY-MM-DD HH:mm"
|
||||
)}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
<ShadTooltip
|
||||
side="top"
|
||||
content={
|
||||
moment(api_keys.last_used_at).format(
|
||||
"YYYY-MM-DD HH:mm"
|
||||
) === "Invalid date"
|
||||
? "Never"
|
||||
: moment(
|
||||
api_keys.last_used_at
|
||||
).format("YYYY-MM-DD HH:mm")
|
||||
}
|
||||
>
|
||||
<div>
|
||||
{moment(api_keys.last_used_at).format(
|
||||
"YYYY-MM-DD HH:mm"
|
||||
) === "Invalid date"
|
||||
? "Never"
|
||||
: moment(
|
||||
api_keys.last_used_at
|
||||
).format("YYYY-MM-DD HH:mm")}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
{api_keys.total_uses}
|
||||
</TableCell>
|
||||
<TableCell className="flex w-[100px] py-2 text-right">
|
||||
<div className="flex">
|
||||
<ConfirmationModal
|
||||
title="Delete"
|
||||
titleHeader="Delete User"
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you sure you want to delete this key? This action cannot be undone."
|
||||
cancelText="Cancel"
|
||||
confirmationText="Delete"
|
||||
icon={"UserMinus2"}
|
||||
data={api_keys.id}
|
||||
index={index}
|
||||
onConfirm={(index, keys) => {
|
||||
handleDeleteKey(keys);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip
|
||||
content="Delete"
|
||||
side="top"
|
||||
>
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="ml-2 h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<SecretKeyModal
|
||||
title="Create new secret key"
|
||||
cancelText="Cancel"
|
||||
confirmationText="Create secret key"
|
||||
icon={"Key"}
|
||||
data={userId}
|
||||
onCloseModal={getKeys}
|
||||
>
|
||||
<Button>
|
||||
<IconComponent name="Plus" className="mr-1 h-5 w-5" />
|
||||
Create new secret key
|
||||
</Button>
|
||||
</SecretKeyModal>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -125,7 +125,6 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
}
|
||||
onClick={(event) => {
|
||||
saveFlow(flow!);
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useContext, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import { CONTROL_LOGIN_STATE } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { getLoggedUser, onLogin } from "../../controllers/API";
|
||||
import { LoginType } from "../../types/api";
|
||||
import {
|
||||
inputHandlerEventType,
|
||||
loginInputStateType,
|
||||
|
|
@ -15,12 +19,49 @@ export default function LoginPage(): JSX.Element {
|
|||
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
|
||||
|
||||
const { password, username } = inputState;
|
||||
const { login, getAuthentication, setUserData, setIsAdmin } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
function signIn() {
|
||||
const user: LoginType = {
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
onLogin(user)
|
||||
.then((user) => {
|
||||
login(user.access_token, user.refresh_token);
|
||||
getUser();
|
||||
navigate("/");
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error signing in",
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getUser() {
|
||||
if (getAuthentication()) {
|
||||
setTimeout(() => {
|
||||
getLoggedUser()
|
||||
.then((user) => {
|
||||
const isSuperUser = user.is_superuser;
|
||||
setIsAdmin(isSuperUser);
|
||||
setUserData(user);
|
||||
})
|
||||
.catch((error) => {});
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
|
|
@ -28,7 +69,7 @@ export default function LoginPage(): JSX.Element {
|
|||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
signIn();
|
||||
const data = Object.fromEntries(new FormData(event.currentTarget));
|
||||
event.preventDefault();
|
||||
}}
|
||||
|
|
@ -92,7 +133,7 @@ export default function LoginPage(): JSX.Element {
|
|||
</Form.Submit>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Link to="">
|
||||
<Link to="/signup">
|
||||
<Button className="w-full" variant="outline">
|
||||
Don't have an account? <b>Sign Up</b>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { FormEvent, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FormEvent, useContext, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import { CONTROL_INPUT_STATE } from "../../constants/constants";
|
||||
import {
|
||||
CONTROL_INPUT_STATE,
|
||||
SIGN_UP_SUCCESS,
|
||||
} from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { addUser } from "../../controllers/API";
|
||||
import {
|
||||
UserInputType,
|
||||
inputHandlerEventType,
|
||||
signUpInputStateType,
|
||||
} from "../../types/components";
|
||||
|
|
@ -15,12 +21,42 @@ export default function SignUp(): JSX.Element {
|
|||
useState<signUpInputStateType>(CONTROL_INPUT_STATE);
|
||||
|
||||
const { password, cnfPassword, username } = inputState;
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
function handleSignup(): void {
|
||||
const { username, password } = inputState;
|
||||
const newUser: UserInputType = {
|
||||
username,
|
||||
password,
|
||||
};
|
||||
addUser(newUser)
|
||||
.then((user) => {
|
||||
setSuccessData({
|
||||
title: SIGN_UP_SUCCESS,
|
||||
});
|
||||
navigate("/login");
|
||||
})
|
||||
.catch((error) => {
|
||||
const {
|
||||
response: {
|
||||
data: { detail },
|
||||
},
|
||||
} = error;
|
||||
setErrorData({
|
||||
title: "Error signing up",
|
||||
list: [detail],
|
||||
});
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Root
|
||||
onSubmit={(event: FormEvent<HTMLFormElement>) => {
|
||||
|
|
@ -120,7 +156,14 @@ export default function SignUp(): JSX.Element {
|
|||
</div>
|
||||
<div className="w-full">
|
||||
<Form.Submit asChild>
|
||||
<Button className="mr-3 mt-6 w-full">Sign up</Button>
|
||||
<Button
|
||||
className="mr-3 mt-6 w-full"
|
||||
onClick={() => {
|
||||
handleSignup();
|
||||
}}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
|
|
|
|||
|
|
@ -1,32 +1,116 @@
|
|||
import { Route, Routes } from "react-router-dom";
|
||||
import { ProtectedAdminRoute } from "./components/authAdminGuard";
|
||||
import { ProtectedRoute } from "./components/authGuard";
|
||||
import { ProtectedLoginRoute } from "./components/authLoginGuard";
|
||||
import { CatchAllRoute } from "./components/catchAllRoutes";
|
||||
import AdminPage from "./pages/AdminPage";
|
||||
import LoginAdminPage from "./pages/AdminPage/LoginPage";
|
||||
import ApiKeysPage from "./pages/ApiKeysPage";
|
||||
import CommunityPage from "./pages/CommunityPage";
|
||||
import FlowPage from "./pages/FlowPage";
|
||||
import HomePage from "./pages/MainPage";
|
||||
import ViewPage from "./pages/ViewPage";
|
||||
import DeleteAccountPage from "./pages/deleteAccountPage";
|
||||
import LoginPage from "./pages/loginPage";
|
||||
import SignUp from "./pages/signUpPage";
|
||||
|
||||
const Router = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/community" element={<CommunityPage />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<HomePage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/community"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<CommunityPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/flow/:id/">
|
||||
<Route path="" element={<FlowPage />} />
|
||||
<Route path="view" element={<ViewPage />} />
|
||||
<Route
|
||||
path=""
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<FlowPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="view"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ViewPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="*" element={<HomePage />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<CatchAllRoute />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
{/* <Route path="/signup" element={<SignUp />} /> */}
|
||||
<Route path="/login/admin" element={<LoginAdminPage />} />
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<LoginPage />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/signup"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<SignUp />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/login/admin"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<LoginAdminPage />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/admin" element={<AdminPage />} />
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<ProtectedAdminRoute>
|
||||
<AdminPage />
|
||||
</ProtectedAdminRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/account">
|
||||
<Route path="delete" element={<DeleteAccountPage />}></Route>
|
||||
<Route
|
||||
path="delete"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<DeleteAccountPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
></Route>
|
||||
<Route
|
||||
path="api-keys"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<ApiKeysPage />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
></Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -62,3 +62,27 @@ export type UploadFileTypeAPI = {
|
|||
file_path: string;
|
||||
flowId: string;
|
||||
};
|
||||
|
||||
export type LoginType = {
|
||||
grant_type?: string;
|
||||
username: string;
|
||||
password: string;
|
||||
scrope?: string;
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
};
|
||||
|
||||
export type LoginAuthType = {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type?: string;
|
||||
};
|
||||
|
||||
export type Users = {
|
||||
id: string;
|
||||
username: string;
|
||||
is_active: boolean;
|
||||
is_superuser: boolean;
|
||||
create_at: Date;
|
||||
updated_at: Date;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ export type signUpInputStateType = {
|
|||
|
||||
export type inputHandlerEventType = {
|
||||
target: {
|
||||
value: string;
|
||||
value: string | boolean;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
|
@ -261,6 +261,29 @@ export type loginInputStateType = {
|
|||
password: string;
|
||||
};
|
||||
|
||||
export type UserInputType = {
|
||||
username: string;
|
||||
password: string;
|
||||
is_active?: boolean;
|
||||
is_superuser?: boolean;
|
||||
id?: string;
|
||||
create_at?: string;
|
||||
updated_at?:string;
|
||||
};
|
||||
|
||||
export type ApiKeyType = {
|
||||
title: string;
|
||||
cancelText: string;
|
||||
confirmationText: string;
|
||||
children: ReactElement;
|
||||
icon: string;
|
||||
data?: any;
|
||||
onCloseModal: () => void;
|
||||
};
|
||||
|
||||
export type ApiKeyInputType = {
|
||||
apikeyname: string;
|
||||
};
|
||||
export type groupedObjType = {
|
||||
family: string;
|
||||
type: string;
|
||||
|
|
@ -508,6 +531,15 @@ export type validationStatusType = {
|
|||
progress: number;
|
||||
valid: boolean;
|
||||
};
|
||||
|
||||
export type ApiKey = {
|
||||
id: string;
|
||||
api_key: string;
|
||||
name: string;
|
||||
created_at: string;
|
||||
last_used_at: string;
|
||||
total_uses: number;
|
||||
};
|
||||
export type fetchErrorComponentType = {
|
||||
message: string;
|
||||
description: string;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ export type alertContextType = {
|
|||
export type darkContextType = {
|
||||
dark: {};
|
||||
setDark: (newState: {}) => void;
|
||||
stars: number;
|
||||
setStars: (stars: number) => void;
|
||||
};
|
||||
|
||||
export type locationContextType = {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import {
|
|||
Edit,
|
||||
Eraser,
|
||||
ExternalLink,
|
||||
Eye,
|
||||
EyeOff,
|
||||
File,
|
||||
FileDown,
|
||||
FileSearch,
|
||||
|
|
@ -33,6 +35,7 @@ import {
|
|||
HelpCircle,
|
||||
Home,
|
||||
Info,
|
||||
Key,
|
||||
Laptop2,
|
||||
Layers,
|
||||
Lightbulb,
|
||||
|
|
@ -61,6 +64,7 @@ import {
|
|||
Undo,
|
||||
Unplug,
|
||||
Upload,
|
||||
UserCog2,
|
||||
UserMinus2,
|
||||
UserPlus2,
|
||||
Users2,
|
||||
|
|
@ -291,5 +295,9 @@ export const nodeIconsLucide: iconsType = {
|
|||
ChevronsLeft,
|
||||
FaGithub,
|
||||
FaApple,
|
||||
EyeOff,
|
||||
Eye,
|
||||
UserCog2,
|
||||
Key,
|
||||
Unplug,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue