Skip to content
Snippets Groups Projects
Commit 5a7a54bb authored by Embruch, Gerd's avatar Embruch, Gerd
Browse files

added hidable routes by gatekeepr

parent 94401a39
Branches
No related tags found
No related merge requests found
Showing
with 106 additions and 69 deletions
@tailwind base;@tailwind components;@tailwind utilities;@layer base{.conceal{@apply opacity-0 h-0 w-0 p-0 m-0 overflow-hidden}label,details{@apply block w-full pb-1 relative cursor-pointer disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-60 lg:min-w-xs lg:w-[calc(1/2*100%-(1*1rem/2))] xl:w-[calc((1/4*100%)-(3*1rem/4))]}fieldset{@apply pb-4 flex flex-wrap lg:gap-x-8 gap-x-4}img,svg,video,canvas,audio,iframe,embed,object{display:inline;vertical-align:middle}:root{--background: 0 0% 100%;--foreground: 0 0% 3.9%;--card: 0 0% 100%;--card-foreground: 0 0% 3.9%;--popover: 0 0% 100%;--popover-foreground: 0 0% 3.9%;--primary: 204 98% 37%;--primary-foreground: 0 0% 98%;--secondary: 0 0% 96.1%;--secondary-foreground: 0 0% 9%;--muted: 0 0% 96.1%;--muted-foreground: 0 0% 45.1%;--accent: 0 0% 96.1%;--accent-foreground: 0 0% 9%;--destructive: 353 100% 44%;--destructive-foreground: 0 0% 98%;--border: 0 0% 89.8%;--input: 0 0% 89.8%;--ring: 0 0% 3.9%;--radius: 0.5rem}.dark{--background: 0 0% 3.9%;--foreground: 0 0% 98%;--card: 0 0% 3.9%;--card-foreground: 0 0% 98%;--popover: 0 0% 3.9%;--popover-foreground: 0 0% 98%;--primary: 0 0% 98%;--primary-foreground: 0 0% 9%;--secondary: 0 0% 14.9%;--secondary-foreground: 0 0% 98%;--muted: 0 0% 14.9%;--muted-foreground: 0 0% 63.9%;--accent: 0 0% 14.9%;--accent-foreground: 0 0% 98%;--destructive: 0 62.8% 30.6%;--destructive-foreground: 0 0% 98%;--border: 0 0% 14.9%;--input: 0 0% 14.9%;--ring: 0 0% 83.1%}*{@apply border-border}body{@apply bg-background text-foreground}}/*# sourceMappingURL=tailwind.presets.min.css.map */
\ No newline at end of file
@tailwind base;@tailwind components;@tailwind utilities;@layer base{.conceal{@apply opacity-0 h-0 w-0 p-0 m-0 overflow-hidden}label,details{@apply block w-full pb-1 relative cursor-pointer disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-60 lg:min-w-xs lg:w-[calc(1/2*100%-(1*1rem/2))] xl:w-[calc((1/4*100%)-(3*1rem/4))]}fieldset{@apply pb-4 flex flex-wrap lg:gap-x-8 gap-4}img,svg,video,canvas,audio,iframe,embed,object{display:inline;vertical-align:middle}:root{--background: 0 0% 100%;--foreground: 0 0% 3.9%;--card: 0 0% 100%;--card-foreground: 0 0% 3.9%;--popover: 0 0% 100%;--popover-foreground: 0 0% 3.9%;--primary: 204 98% 37%;--primary-foreground: 0 0% 98%;--secondary: 0 0% 96.1%;--secondary-foreground: 0 0% 9%;--muted: 0 0% 96.1%;--muted-foreground: 0 0% 45.1%;--accent: 0 0% 96.1%;--accent-foreground: 0 0% 9%;--destructive: 353 100% 44%;--destructive-foreground: 0 0% 98%;--border: 0 0% 89.8%;--input: 0 0% 89.8%;--ring: 0 0% 3.9%;--radius: 0.5rem}.dark{--background: 0 0% 3.9%;--foreground: 0 0% 98%;--card: 0 0% 3.9%;--card-foreground: 0 0% 98%;--popover: 0 0% 3.9%;--popover-foreground: 0 0% 98%;--primary: 0 0% 98%;--primary-foreground: 0 0% 9%;--secondary: 0 0% 14.9%;--secondary-foreground: 0 0% 98%;--muted: 0 0% 14.9%;--muted-foreground: 0 0% 63.9%;--accent: 0 0% 14.9%;--accent-foreground: 0 0% 98%;--destructive: 0 62.8% 30.6%;--destructive-foreground: 0 0% 98%;--border: 0 0% 14.9%;--input: 0 0% 14.9%;--ring: 0 0% 83.1%}*{@apply border-border}body{@apply bg-background text-foreground}}/*# sourceMappingURL=tailwind.presets.min.css.map */
\ No newline at end of file
{"version":3,"sources":["../sass/tailwind.presets.scss"],"names":[],"mappings":"AAAA,cAAA,CACA,oBAAA,CACA,mBAAA,CACA,YACE,SACE,gDAAA,CAEF,cAEE,6MAAA,CAEF,SACE,6CAAA,CAEF,+CAQE,cAAA,CACA,qBAAA,CAEF,MACE,uBAAA,CACA,uBAAA,CAEA,iBAAA,CACA,4BAAA,CAEA,oBAAA,CACA,+BAAA,CAGA,sBAAA,CACA,8BAAA,CAEA,uBAAA,CACA,+BAAA,CAEA,mBAAA,CACA,8BAAA,CAEA,oBAAA,CACA,4BAAA,CAEA,2BAAA,CACA,kCAAA,CAEA,oBAAA,CACA,mBAAA,CACA,iBAAA,CAEA,gBAAA,CAGF,MACE,uBAAA,CACA,sBAAA,CAEA,iBAAA,CACA,2BAAA,CAEA,oBAAA,CACA,8BAAA,CAEA,mBAAA,CACA,6BAAA,CAEA,uBAAA,CACA,gCAAA,CAEA,mBAAA,CACA,8BAAA,CAEA,oBAAA,CACA,6BAAA,CAEA,4BAAA,CACA,kCAAA,CAEA,oBAAA,CACA,mBAAA,CACA,kBAAA,CAGF,EACE,oBAAA,CAEF,KACE,oCAAA,CAAA","file":"tailwind.presets.min.css"}
\ No newline at end of file
{"version":3,"sources":["../sass/tailwind.presets.scss"],"names":[],"mappings":"AAAA,cAAA,CACA,oBAAA,CACA,mBAAA,CACA,YACE,SACE,gDAAA,CAEF,cAEE,6MAAA,CAEF,SACE,2CAAA,CAEF,+CAQE,cAAA,CACA,qBAAA,CAEF,MACE,uBAAA,CACA,uBAAA,CAEA,iBAAA,CACA,4BAAA,CAEA,oBAAA,CACA,+BAAA,CAGA,sBAAA,CACA,8BAAA,CAEA,uBAAA,CACA,+BAAA,CAEA,mBAAA,CACA,8BAAA,CAEA,oBAAA,CACA,4BAAA,CAEA,2BAAA,CACA,kCAAA,CAEA,oBAAA,CACA,mBAAA,CACA,iBAAA,CAEA,gBAAA,CAGF,MACE,uBAAA,CACA,sBAAA,CAEA,iBAAA,CACA,2BAAA,CAEA,oBAAA,CACA,8BAAA,CAEA,mBAAA,CACA,6BAAA,CAEA,uBAAA,CACA,gCAAA,CAEA,mBAAA,CACA,8BAAA,CAEA,oBAAA,CACA,6BAAA,CAEA,4BAAA,CACA,kCAAA,CAEA,oBAAA,CACA,mBAAA,CACA,kBAAA,CAGF,EACE,oBAAA,CAEF,KACE,oCAAA,CAAA","file":"tailwind.presets.min.css"}
\ No newline at end of file
......@@ -10,7 +10,7 @@
@apply block w-full pb-1 relative cursor-pointer disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-60 lg:min-w-xs lg:w-[calc(1/2*100%-(1*1rem/2))] xl:w-[calc((1/4*100%)-(3*1rem/4))];
}
fieldset {
@apply pb-4 flex flex-wrap lg:gap-x-8 gap-x-4;
@apply pb-4 flex flex-wrap lg:gap-x-8 gap-4;
}
img,
svg,
......
import React from 'react';
import { useAuth } from '../../../contexts/Auth/AuthState';
import DesktopLink from './DesktopLink';
function DesktopNav({ filteredSitemap }) {
// #################################
// HOOKS
// #################################
// ### CONNECT AUTH CONTEXT
const { currentUser } = useAuth();
// #################################
// FUNCTIONS
......@@ -12,9 +15,15 @@ function DesktopNav({ filteredSitemap }) {
// recursively render given menu
const renderMenu = (menu, parent = null) => {
if (!menu) return;
return menu.map((item, idx) => (
return menu.filter((item) => {
// dont show items that are above the current user role
if (item.gateKeeper && currentUser && currentUser.role < item.gateKeeper) return;
return item;
}).map((item, idx) => (
// render menu items
<li key={`link-${idx}`} className="relative" >
{item.children?.length ? (
{
item.children?.length ? (
<>
<DesktopLink to={parent ? `${parent.path}/${item.path}` : item.path}>{item.title}</DesktopLink>
<ul className="absolute z-50 h-0 overflow-y-hidden bg-UhhBlue border-UhhBlue hover:h-auto peer-hover:h-auto">
......@@ -23,9 +32,11 @@ function DesktopNav({ filteredSitemap }) {
</>
) : (
<DesktopLink to={parent ? `${parent.path}/${item.path}` : item.path}>{item.title}</DesktopLink>
)}
)
}
</li>
));
};
// #################################
......
import React, { useEffect, useState } from 'react';
import { sitemap } from "/src/routes/Sitemap";
import { useAuth } from '../../../contexts/Auth/AuthState';
import DesktopNav from './DesktopNav';
import MobileNav from './MobileNav';
import { sitemap } from "/src/routes/Sitemap";
function Navbar(props) {
......@@ -11,6 +12,9 @@ function Navbar(props) {
// ### FILTERED SITEMAP
const [filteredSitemap, setFilteredSitemap] = useState([]);
// ### CONNECT AUTH CONTEXT
const { currentUser } = useAuth();
useEffect(() => {
// fetch all links for navbars
const [overall] = sitemap.filter((item) => item.title === 'MenuBar');
......@@ -22,7 +26,12 @@ function Navbar(props) {
function flatFilter(nestedProp, searchKey, searchValue, arr) {
return arr.filter(o => {
// slightly customized for searchKey = object
const keep = o[searchKey] && o[searchKey].hasOwnProperty(searchValue);
let keep = o[searchKey] && o[searchKey].hasOwnProperty(searchValue);
// dont show items that are above the current user role
if (o.gateKeeper && currentUser && currentUser.role < o.gateKeeper) keep = false;
if (keep && o[nestedProp]) {
o[nestedProp] = flatFilter(nestedProp, searchKey, searchValue, o[nestedProp]);
}
......
import React, { useState } from 'react';
import { useAuth } from '/src/contexts/Auth/AuthState';
import api from '/src/utils/AxiosConfig';
import { RiDeleteBinLine, RiFileInfoLine, RiMoreLine, RiRefreshLine } from 'react-icons/ri';
import ConfirmBox from '/src/components/boxes/ConfirmBox';
import InfoBox from '/src/components/boxes/InfoBox';
import CustomTable from '/src/components/table/customTable';
import { mergeBackendValidation, setFlashMsg } from '/src/utils/ErrorHandling';
import { Button } from '/src/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
......@@ -13,8 +12,9 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "/src/components/ui/dropdown-menu";
import { Button } from '/src/components/ui/button';
import { RiDeleteBinLine, RiFileInfoLine, RiMoreLine, RiRefreshLine } from 'react-icons/ri';
import { useAuth } from '/src/contexts/Auth/AuthState';
import api from '/src/utils/AxiosConfig';
import { mergeBackendValidation, setFlashMsg } from '/src/utils/ErrorHandling';
function Models({ data, setData }) {
......@@ -152,7 +152,7 @@ function Models({ data, setData }) {
// OUTPUT
// #################################
return (
<div>
<div className='self-start bg-white border border-UhhLightGrey rounded-lg shadow-lg p-3'>
{/* table */}
<CustomTable columns={columns} data={data} title='installed models' />
......
......@@ -49,8 +49,9 @@ function NewModel({ data, setData }) {
// OUTPUT
// #################################
return (
<div>
<>
{(currentUser?.role >= 2) ?
<div className='self-start bg-white border border-UhhLightGrey rounded-lg shadow-lg p-3'>
<FormProvider {...methods} >
<Heading level="4">install new model</Heading>
<form onSubmit={methods.handleSubmit(handleInstall)} className=''>
......@@ -58,9 +59,10 @@ function NewModel({ data, setData }) {
<Submit size='sm' value={methods.formState.isSubmitting ? 'installing...' : 'install model'} />
</form>
</FormProvider>
: null}
</div>
: null}
</>
);
}
......
import React, { useEffect, useState } from 'react';
import { mergeBackendValidation } from '../../../utils/ErrorHandling';
import api from '../../../utils/AxiosConfig';
import { RiWifiFill, RiWifiOffFill } from "react-icons/ri";
import Heading from '../../../components/font/Heading';
import api from '../../../utils/AxiosConfig';
import { mergeBackendValidation } from '../../../utils/ErrorHandling';
function AIStatus() {
// #################################
......@@ -43,11 +43,13 @@ function AIStatus() {
// OUTPUT
// #################################
return (
<Heading level="4">status:
<div className='self-start bg-white border border-UhhLightGrey rounded-lg shadow-lg p-3'>
<Heading level="4">status <br />
{status ?
<RiWifiFill className='ml-4 text-UhhBlue' title='AI backend reachable' />
: <RiWifiOffFill className='ml-4 text-UhhRed' title='AI backend offline' />}
</Heading>
</div>
);
}
......
......@@ -62,7 +62,6 @@ function Embeddings() {
<Update setStatus={setStatus} />
{/* delete embeddings */}
<Delete setStatus={setStatus} />
</fieldset>
</section>
);
......
......@@ -44,9 +44,9 @@ function Delete({ setStatus }) {
// OUTPUT
// #################################
return (
<div>
{(currentUser?.role >= 2) ?
<>
{(currentUser?.role >= 2) ?
<div className='self-start bg-white border border-UhhLightGrey rounded-lg shadow-lg p-3'>
<FormProvider {...methods} >
<Heading level="4">Delete Embedding Collection</Heading>
<form onSubmit={methods.handleSubmit(handleConfirm)} className='md:w-1/3'>
......@@ -57,10 +57,10 @@ function Delete({ setStatus }) {
<ConfirmBox confirmDialog={confirmDialog} closeDialog={() => setConfirmDialog({ ...confirmDialog, open: false })} handleProceed={() => { handleDelete(confirmDialog.idToDelete); }} />
</>
</div>
: null}
</div>
</>
);
}
......
......@@ -15,7 +15,7 @@ function Status({ status }) {
// OUTPUT
// #################################
return (
<div>
<div className='self-start bg-white border border-UhhLightGrey rounded-lg shadow-lg p-3'>
<Heading level="4">Status</Heading>
<JsonToHtmlDL jsonContent={status} />
</div>
......
......@@ -46,8 +46,9 @@ function Update({ setStatus }) {
// OUTPUT
// #################################
return (
<div>
<>
{(currentUser?.role >= 2) ?
<div className='self-start bg-white border border-UhhLightGrey rounded-lg shadow-lg p-3'>
<FormProvider {...methods}>
<Heading level="4">Update Embeddings
<Tooltip><p className='text-base'>based on local RAG Files</p></Tooltip>
......@@ -60,8 +61,9 @@ function Update({ setStatus }) {
<JsonToHtmlDL jsonContent={data} />
</details>
</FormProvider>
: null}
</div>
: null}
</>
);
}
......
......@@ -11,7 +11,7 @@ import { ROLES } from '../UserTypes';
import { mergeBackendValidation, setFlashMsg } from '/src/utils/ErrorHandling';
function User({ user, idx, handleDelete }) {
function User({ user, idx, setConfirmDialog }) {
// #################################
// VALIDATION SCHEMA
// #################################
......@@ -67,7 +67,7 @@ function User({ user, idx, handleDelete }) {
{currentUser?.role >= 2 ?
<>
<h2 className='text-Uhh-Grey font-bold text-2xl flex justify-end'>
<RiDeleteBinLine className='cursor-pointer hover:text-UhhRed' title='delete user' onClick={() => handleDelete(user.id, idx)} />
<RiDeleteBinLine className='cursor-pointer hover:text-UhhRed' title='delete user' onClick={() => setConfirmDialog({ open: true, idToDelete: user.id, displayName: user.username })} />
</h2>
<FormProvider {...methods} >
<form onSubmit={methods.handleSubmit(handleSendForm)} noValidate>
......
import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import ConfirmBox from '../../../components/boxes/ConfirmBox';
import Heading from '../../../components/font/Heading';
import api from '../../../utils/AxiosConfig';
import { setFlashMsg } from '../../../utils/ErrorHandling';
import User from './User';
import { mergeBackendValidation, setFlashMsg } from '/src/utils/ErrorHandling';
import { mergeBackendValidation } from '/src/utils/ErrorHandling';
function Users() {
......@@ -13,6 +15,9 @@ function Users() {
// ### USERS
const [users, setUsers] = useState();
// ### CONFIRM DIALOG
const [confirmDialog, setConfirmDialog] = useState({ open: false, item: {} });
// ### FETCH USERS
useEffect(() => {
// mount
......@@ -39,16 +44,19 @@ function Users() {
// FUNCTIONS
// #################################
// ### DELETE USERS
const handleDelete = async (id, idx) => {
const handleDelete = async (id) => {
try {
// delete in backend
const result = await api.delete(`/users/${id}`);
// delete in frontend
const list = [...users];
list.splice(idx, 1);
const list = users.filter(user => user.id !== id);
setUsers(list);
setFlashMsg(result.data?.message);
} catch (error) {
console.log("🚀 ~ handleDelete ~ error:", error);
mergeBackendValidation(error.response.status, error.response.data, methods.setError);
}
};
......@@ -67,13 +75,15 @@ function Users() {
? (
<div className='flex flex-wrap justify-items-stretch'>
{users.map((user, idx) =>
<User key={user._id} idx={idx} user={user} handleDelete={handleDelete} />
<User key={user._id} idx={idx} user={user} setConfirmDialog={setConfirmDialog} />
)}
</div>
) : <p>No users to display</p>
}
</div>
{/* confirmDialog */}
<ConfirmBox confirmDialog={confirmDialog} closeDialog={() => setConfirmDialog({ ...confirmDialog, open: false })} handleProceed={() => { handleDelete(confirmDialog.idToDelete); }} />
</>
);
}
......
......@@ -40,7 +40,7 @@ export const sitemap = [{
},
// USER
{
title: 'Users', path: '/users', element: loadComponent('User/List/Users', true, true), handle: { crumb: () => <Link to="/users">Users</Link> }
title: 'Users', gateKeeper: 4, path: '/users', element: loadComponent('User/List/Users', true, true), handle: { crumb: () => <Link to="/users">Users</Link> }
},
// PROFILE
{
......
......@@ -6,7 +6,7 @@ import PrivateRoute from './PrivateRoute';
* This method will be used in routes so that the files are loaded only
* When users are on that route
*/
export function loadComponent(componentPath, lazyLoad, privateRoute) {
export function loadComponent(componentPath, lazyLoad, privateRoute, gateKeeper = 0) {
lazyLoad = typeof lazyLoad !== "undefined" ? lazyLoad : true;
privateRoute = typeof privateRoute !== "undefined" ? privateRoute : false;
......@@ -23,3 +23,5 @@ export function loadComponent(componentPath, lazyLoad, privateRoute) {
// Wrapping around the suspense component is mandatory
return element;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment