Skip to content

Commit

Permalink
add multitenancy
Browse files Browse the repository at this point in the history
  • Loading branch information
Safouen Turki committed Oct 12, 2024
1 parent 64a7bc3 commit 04a432a
Show file tree
Hide file tree
Showing 16 changed files with 474 additions and 150 deletions.
10 changes: 7 additions & 3 deletions .env
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
VITE_BACKEND_PORT=9000
VITE_BACKEND_NAME=localhost
# The port number on which the backend server is running
VITE_BACKEND_PORT=80

# The IP address or hostname of the backend server
VITE_BACKEND_NAME=212.2.246.128

# The timeout duration (in milliseconds) for API requests
VITE_API_TIMEOUT=5000
VITE_APP_VERSION=0.0.1
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# The port number on which the backend server is running
VITE_BACKEND_PORT=80

# The IP address or hostname of the backend server
VITE_BACKEND_NAME=ip-212-2-246-128.eu

# The timeout duration (in milliseconds) for API requests
VITE_API_TIMEOUT=5000

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ default : `localhost`

`set VITE_BACKEND_NAME= ui-backend`


### Configure the protocol

to change the protocol to `https` set the following environment variable
Expand Down
10 changes: 10 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ import { router } from "./Router";
import { TooltipProvider } from "@/components/ui/tooltip";
import { toast, Toaster } from "sonner";
import { QueryClient, QueryClientProvider } from "react-query";
import { isAxiosError } from "axios";

export default function App() {
const queryClient = new QueryClient();

queryClient.setDefaultOptions({
queries: {
onError: (error: unknown) => {
if (error instanceof Error) {
if (
isAxiosError(error) &&
error.response &&
error.response.status === 401
) {
window.location.href = "/login?error=unauthorized";
toast.error("Unauthorized, please login again");
}
toast.error(`Something went wrong: ${error.message}`);
}
},
Expand Down
25 changes: 19 additions & 6 deletions src/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,40 @@ import NoMatch from "./modules/fallback/NoMatch";
import ClustersPage from "@/modules/clusters/clusters-list/ClustersPage";
import { appConfig } from "@/config/app";
import { ClusterInfoById } from "@/modules/clusters/cluster-information/ClusterInfoById";
import { Authentication } from "@/modules/authentication/Authentication";
const defaultTab = appConfig.defaultType;
const defaultPage = appConfig.defaultPage;
export const router = createBrowserRouter([
{
path: "/",
element: <Navigate to={"/login"} />,
},
{
path: "/login",
element: <Authentication />,
},
{
path: "/sveltos",
element: <Applayout />,
children: [
{
path: "/",
element: <Navigate to={`/clusters/${defaultTab}/${defaultPage}`} />,
path: "/sveltos",
element: (
<Navigate to={`/sveltos/clusters/${defaultTab}/${defaultPage}`} />
),
},
{
path: "/clusters",
element: <Navigate to={`/clusters/${defaultTab}/${defaultPage}`} />,
path: "/sveltos/clusters",
element: (
<Navigate to={`/sveltos/clusters/${defaultTab}/${defaultPage}`} />
),
},
{
path: "/clusters/:tab/:pageNumber",
path: "/sveltos/clusters/:tab/:pageNumber",
element: <ClustersPage />,
},
{
path: "/cluster/:tab/:namespace/:name",
path: "/sveltos/cluster/:tab/:namespace/:name",
element: <ClusterInfoById />,
},
{
Expand Down
4 changes: 4 additions & 0 deletions src/api-client/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const client = axios.create({
});

client.interceptors.request.use(function (config) {
const token = localStorage.getItem("authToken");
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
}
config.headers["Content-Type"] = "application/json";
return config;
});
Expand Down
303 changes: 303 additions & 0 deletions src/components/assets/logo/logo.tsx

Large diffs are not rendered by default.

18 changes: 15 additions & 3 deletions src/components/layouts/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from "react";
import { NavLink, useLocation } from "react-router-dom";
import { NavLink, useLocation, useNavigate } from "react-router-dom";
import { cn } from "@/lib/utils";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Icons } from "@/components/icons";
Expand All @@ -20,7 +20,7 @@ import {
HamburgerMenuIcon,
} from "@radix-ui/react-icons";
import { ScrollArea } from "@radix-ui/react-scroll-area";
import { Logo } from "../logo";
import { Logo } from "../assets/logo/logo";
import {
Accordion,
AccordionContent,
Expand All @@ -29,17 +29,23 @@ import {
} from "@/components/ui/accordion";
import { ModeToggle } from "@/components/mode-toggle";
import { Badge } from "@/components/ui/badge";
import { LogOutIcon } from "lucide-react";

export function Header() {
const [open, setOpen] = useState(false);
const location = useLocation();
const version = import.meta.env.VITE_APP_VERSION;
const isPublicPreview = (version?.split(".")[0] ?? "") === "0" || true;
const navigate = useNavigate();
const handleLogout = () => {
localStorage.clear();
navigate("/login");
};
return (
<header className="supports-backdrop-blur:bg-background/60 sticky top-0 z-50 w-full border-b bg-background/90 backdrop-blur">
<div className="container px-4 md:px-8 flex h-14 items-center">
<div className="mr-4 hidden md:flex">
<NavLink to="/" className="mr-6 flex items-center space-x-2">
<NavLink to="/sveltos" className="mr-6 flex items-center space-x-2">
<Logo />
{version && (
<Badge
Expand Down Expand Up @@ -256,6 +262,12 @@ export function Header() {
</div>
</a>
<DividerVerticalIcon />

<Button variant={"outline"} onClick={handleLogout} size={"sm"}>
<LogOutIcon className={"h-4 w-4 mx-1"} />
Logout
</Button>

{/* Hide User menu
<DropdownMenu>
Expand Down
128 changes: 0 additions & 128 deletions src/components/logo.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/config/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface NavItemWithChildren extends NavItem {
export const mainMenu: NavItemWithChildren[] = [
{
title: "Clusters",
to: "/clusters",
to: "/sveltos/clusters",
icon: <Boxes className={"w-4 h-4"} />,
},
{
Expand Down
81 changes: 81 additions & 0 deletions src/modules/authentication/Authentication.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Logo } from "@/components/assets/logo/logo";
import useAuth from "@/modules/authentication/hooks/useAuth";
import { FormEvent, useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react";

export const Authentication = () => {
const { authenticate } = useAuth();
const [token, setToken] = useState("");

const handleSubmit = (event: FormEvent) => {
event.preventDefault();
authenticate(token);
};
const [errorMessage, setErrorMessage] = useState("");
const location = useLocation();
useEffect(() => {
const params = new URLSearchParams(location.search);
if (params.get("error") === "unauthorized") {
setErrorMessage("Unauthorized, please login again");
}
}, [location]);

return (
<div className={"mx-auto my-auto"}>
{errorMessage && (
<Alert variant="destructive" className={"my-4"}>
<AlertCircle className="h-4 w-4" />
<AlertTitle>Unauthorized</AlertTitle>
<AlertDescription>
Failed to login. Please try again.
</AlertDescription>
</Alert>
)}
<Card className=" w-[500px]">
<CardHeader>
<Logo full className={"h-36 w-36 mx-auto"} />

<CardTitle className="text-xl">Login</CardTitle>

<CardDescription>
Use your authentication token to log in
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="token">Authorization Token</Label>
<Input
id="token"
value={token}
onChange={(e) => setToken(e.target.value)}
required
/>
</div>
<Button type="submit" className="w-full">
Login
</Button>
</form>
<div className="mt-4 text-center text-sm">
Need help obtaining your token?
<a href="#" className="underline mx-1">
Learn more
</a>
</div>
</CardContent>
</Card>
</div>
);
};
Loading

0 comments on commit 04a432a

Please sign in to comment.