introduce admin area

introduce admin area
first poc for connecting the authentik api

Co-authored-by: Philipp Rothmann <philipprothmann@posteo.de>
Reviewed-on: #2
This commit is contained in:
philipp 2022-11-08 16:36:16 +01:00
parent 8d760e588f
commit 44e4e4eb42
35 changed files with 367 additions and 133 deletions

View file

@ -13,7 +13,6 @@ import { Login } from './modules/login';
import { Users } from './modules/users/Users';
import { AppSingle } from './modules/apps/AppSingle';
import { Apps } from './modules/apps/Apps';
import { DashboardLIT } from './modules/dashboard/DashboardLIT';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function App() {
@ -53,7 +52,7 @@ function App() {
) : (
<Layout>
<Routes>
<Route path="/dashboard" element={<DashboardLIT />} />
<Route path="/dashboard" element={<Dashboard />} />
{apps.map((app) => (
<Route key={app.name} path={app.slug} element={<AppIframe app={app} />} />
))}

View file

@ -1,4 +1,4 @@
import React, { Fragment, useMemo, useState } from 'react';
import React, { Fragment, useMemo } from 'react';
import { Disclosure, Menu, Transition } from '@headlessui/react';
import { MenuIcon, XIcon } from '@heroicons/react/outline';
import { useAuth } from 'src/services/auth';
@ -6,16 +6,10 @@ import Gravatar from 'react-gravatar';
import { Link, useLocation } from 'react-router-dom';
import clsx from 'clsx';
import { useApps } from 'src/services/apps';
import _ from 'lodash';
import { UserModal } from '../UserModal';
const HYDRA_LOGOUT_URL = `${process.env.REACT_APP_HYDRA_PUBLIC_URL}/oauth2/sessions/logout`;
const navigation = [
{ name: 'Dashboard', to: '/dashboard', requiresAdmin: false },
{ name: 'Users', to: '/users', requiresAdmin: true },
{ name: 'Apps', to: '/apps', requiresAdmin: true },
{ name: '', to: '/users', requiresAdmin: true },
// { name: 'Apps', to: '/apps', requiresAdmin: true },
];
function classNames(...classes: any[]) {
@ -34,32 +28,15 @@ function filterNavigationByDashboardRole(isAdmin: boolean) {
interface HeaderProps {}
const HeaderLIT: React.FC<HeaderProps> = () => {
const [currentUserModal, setCurrentUserModal] = useState(false);
const [currentUserId, setCurrentUserId] = useState(null);
const { logOut, currentUser, isAdmin } = useAuth();
const { pathname } = useLocation();
const { apps, loadApps, appTableLoading } = useApps();
const currentUserModalOpen = (id: any) => {
setCurrentUserId(id);
setCurrentUserModal(true);
};
const currentUserModalClose = () => {
setCurrentUserModal(false);
setCurrentUserId(null);
};
const { apps } = useApps();
const navigationItems = filterNavigationByDashboardRole(isAdmin);
const signOutUrl = useMemo(() => {
const { hostname } = window.location;
// If we are developing locally, we need to use the init cluster's public URL
if (hostname === 'localhost') {
return HYDRA_LOGOUT_URL;
}
return `https://${hostname.replace(/^dashboard/, 'sso')}/oauth2/sessions/logout`;
// @ts-ignore
return window.env.REACT_APP_SSO_LOGOUT_URL;
}, []);
return (
@ -102,6 +79,21 @@ const HeaderLIT: React.FC<HeaderProps> = () => {
{app.name}
</Link>
))}
{/* {navigationItems.map((item) => (
<Link
key={item.name}
to={item.to}
className={clsx(
'border-primary-50 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium litbutton',
{
'border-primary-500 litbutton-active hover:border-gray-300 inline-flex items-center px-1 pt-1 text-sm font-medium':
pathname.includes(item.to),
},
)}
>
{item.name}
</Link>
))} */}
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
@ -167,10 +159,6 @@ const HeaderLIT: React.FC<HeaderProps> = () => {
</div>
)}
</Disclosure>
{currentUserModal && (
<UserModal open={currentUserModal} onClose={currentUserModalClose} userId={currentUserId} setUserId={_.noop} />
)}
</>
);
};

View file

@ -1,2 +1 @@
export { default as Header } from './Header';
export { default as HeaderLIT } from './HeaderLIT';
export { default as Header } from './HeaderLIT';

View file

@ -1,10 +1,10 @@
import React from 'react';
import { HeaderLIT } from '../Header';
import { Header } from '../Header';
const Layout: React.FC = ({ children }) => {
return (
<>
<HeaderLIT />
<Header />
{children}
</>

View file

@ -1,5 +1,5 @@
export { Layout } from './Layout';
export { Header, HeaderLIT } from './Header';
export { Header } from './Header';
export { Table } from './Table';
export { Banner } from './Banner';
export { Tabs } from './Tabs';

View file

@ -1,9 +1,13 @@
import React, { useEffect } from 'react';
import { useApps } from 'src/services/apps';
import { DashboardCardLIT } from './components/DashboardCard/DashboardCardLIT';
import { AppStatusEnum, useApps } from 'src/services/apps';
import { useAuth } from 'src/services/auth';
import { DashboardUtility } from './components';
import { DashboardCard } from './components/DashboardCard/DashboardCardLIT';
import { UTILITY_APPS } from './consts';
export const DashboardLIT: React.FC = () => {
export const Dashboard: React.FC = () => {
const { apps, loadApps, appTableLoading } = useApps();
const { isAdmin } = useAuth();
// Tell React to load the apps
useEffect(() => {
@ -21,9 +25,27 @@ export const DashboardLIT: React.FC = () => {
</div>
<div className="max-w-7xl mx-auto py-4 px-3 sm:px-6 lg:px-8 h-full flex-grow">
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-4 lg:grid-cols-4 mb-10">
{!appTableLoading && apps.map((app) => <DashboardCardLIT app={app} key={app.name} />)}
{!appTableLoading &&
apps
.filter((app) => UTILITY_APPS.indexOf(app.slug) === -1)
.map((app) => <DashboardCard app={app} key={app.name} />)}
</div>
</div>
{isAdmin && (
<div className="max-w-4xl mx-auto py-4 sm:px-6 lg:px-8 h-full flex-grow">
<div className="pb-4 border-b border-gray-200 sm:flex sm:items-center">
<h3 className="text-lg leading-6 font-medium text-gray-900">Administration</h3>
</div>
<dl className="mt-5 grid grid-cols-1 gap-2 sm:grid-cols-2">
{apps
.filter((app) => UTILITY_APPS.indexOf(app.slug) !== -1 && app.url !== null)
.map((app) => (
<DashboardUtility item={app} key={app.name} />
))}
</dl>
</div>
)}
</div>
);
};

View file

@ -1,7 +1,7 @@
import React from 'react';
import { Link } from 'react-router-dom';
export const DashboardCardLIT: React.FC<any> = ({ app }: { app: any }) => {
export const DashboardCard: React.FC<any> = ({ app }: { app: any }) => {
return (
<>
<Link

View file

@ -1 +1 @@
export { DashboardCard } from './DashboardCard';
export { DashboardCard } from './DashboardCardLIT';

View file

@ -0,0 +1,36 @@
import React, { useState, useEffect } from 'react';
import ReactMarkdown from 'react-markdown';
import { Link } from 'react-router-dom';
export const DashboardUtility: React.FC<any> = ({ item }: { item: any }) => {
const [content, setContent] = useState('');
useEffect(() => {
fetch(item.markdownSrc)
.then((res) => res.text())
.then((md) => {
return setContent(md);
})
.catch(() => {});
}, [item.markdownSrc]);
return (
<Link
to={`/${item.slug}`}
key={item.name}
rel="noreferrer"
className="bg-white rounded-lg overflow-hidden sm:p-2 flex items-center group"
>
<div className="w-16 h-16 flex items-center justify-center bg-primary-100 group-hover:bg-primary-200 transition-colors rounded-lg mr-4">
{item.icon && <item.icon className="h-6 w-6 text-primary-900" aria-hidden="true" />}
{item.assetSrc && <img className="h-6 w-6" src={item.assetSrc} alt={item.name} />}
</div>
<div>
<dt className="truncate text-sm leading-5 font-medium">{item.name}</dt>
<dd className="mt-1 text-gray-500 text-sm leading-5 font-normal">
<ReactMarkdown>{content}</ReactMarkdown>
</dd>
</div>
</Link>
);
};

View file

@ -1 +1 @@
export { DashboardUtility } from './DashboardUtility';
export { DashboardUtility } from './DashboardUtilityLIT';

View file

@ -10,7 +10,7 @@ export const DASHBOARD_QUICK_ACCESS = [
];
/** Apps that should not be shown on the dashboard */
export const HIDDEN_APPS = ['dashboard', 'velero'];
export const HIDDEN_APPS = ['dashboard'];
/** Apps that should be shown under "Utilities" */
export const UTILITY_APPS = ['monitoring'];
export const UTILITY_APPS = ['authentik', 'zammad'];

View file

@ -1 +1 @@
export { Dashboard } from './Dashboard';
export { Dashboard } from './DashboardLIT';

View file

@ -53,6 +53,11 @@ export const Users: React.FC = () => {
const columns: any = React.useMemo(
() => [
{
Header: 'Username',
accessor: 'preferredUsername',
width: 'auto',
},
{
Header: 'Name',
accessor: 'name',
@ -75,11 +80,12 @@ export const Users: React.FC = () => {
if (isAdmin) {
return (
<div className="text-right lg:opacity-0 group-hover:opacity-100 transition-opacity">
<div className="text-right lg:opacity-0 transition-opacity">
<button
disabled
onClick={() => configureModalOpen(row.original.id)}
type="button"
className="inline-flex items-center px-4 py-2 border border-gray-200 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
className="inline-flex items-center px-4 py-2 border border-gray-200 shadow-sm text-sm font-medium rounded-md text-gray bg-gray hover:bg-gray focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
>
<CogIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
Configure
@ -109,17 +115,19 @@ export const Users: React.FC = () => {
{isAdmin && (
<div className="mt-3 sm:mt-0 sm:ml-4">
<button
disabled
onClick={() => configureModalOpen(null)}
type="button"
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-700 hover:bg-primary-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-800 mx-5 "
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-200 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-800 mx-5 "
>
<PlusIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
Add new user
</button>
<button
disabled
onClick={() => setMultipleUsersModal(true)}
type="button"
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-700 hover:bg-primary-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-800"
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-200 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-800"
>
<ViewGridAddIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
Add new users