Merge branch 'feat/hide-unused-features' into 'main'
Feat/hide unused features See merge request stackspin/dashboard!20
This commit is contained in:
commit
8bf2460f65
28 changed files with 3357 additions and 20314 deletions
16443
package-lock.json
generated
16443
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,6 @@
|
|||
"@headlessui/react": "^1.3.0",
|
||||
"@heroicons/react": "^1.0.3",
|
||||
"@hookform/resolvers": "^2.6.1",
|
||||
"@reach/router": "^1.3.4",
|
||||
"@tailwindcss/forms": "^0.3.3",
|
||||
"@tailwindcss/typography": "^0.4.1",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
|
@ -23,11 +22,14 @@
|
|||
"prismjs": "^1.24.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-gravatar": "^2.6.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hook-form": "^7.22.0",
|
||||
"react-hot-toast": "^2.0.0",
|
||||
"react-markdown": "^7.0.1",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "6.2.1",
|
||||
"react-router": "6.2.1",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-simple-code-editor": "^0.11.0",
|
||||
"react-table": "^7.7.0",
|
||||
|
@ -69,9 +71,10 @@
|
|||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.171",
|
||||
"@types/prismjs": "^1.16.6",
|
||||
"@types/reach__router": "^1.3.9",
|
||||
"@types/react": "^17.0.18",
|
||||
"@types/react-gravatar": "^2.6.10",
|
||||
"@types/react-helmet": "^6.1.2",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-table": "^7.7.2",
|
||||
"autoprefixer": "^9",
|
||||
"dotenv": "^10.0.0",
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="160" height="160" fill="#F5455C"/>
|
||||
<path d="M63.2762 45.8537C65.9188 47.3247 68.3617 49.1288 70.5451 51.2216C74.0286 50.5972 77.5608 50.285 81.0998 50.2888C91.8454 50.2888 102.023 53.1108 109.764 58.224C113.516 60.6466 116.718 63.8284 119.165 67.5644C121.666 71.3356 123 75.7604 123 80.2855C123 84.8107 121.666 89.2354 119.165 93.0066C116.697 96.7535 113.47 99.9397 109.691 102.359C101.95 107.476 91.7729 110.282 81.0273 110.282C77.5058 110.291 73.9905 109.987 70.5227 109.374C68.346 111.474 65.902 113.279 63.2538 114.741C59.2822 116.811 54.8747 117.907 50.3959 117.936C45.9171 117.966 41.4956 116.928 37.4971 114.91C37.4971 114.91 48.4004 105.69 46.5837 97.6219C44.2564 95.3619 42.3972 92.6654 41.1124 89.6865C39.8277 86.7076 39.1426 83.5048 39.0963 80.261C39.2156 73.7542 41.9078 67.5597 46.5837 63.0333C48.3524 54.9531 37.4971 45.7332 37.4971 45.7332C41.4946 43.704 45.9182 42.6566 50.4012 42.6775C54.8842 42.6985 59.2977 43.7872 63.2762 45.8537V45.8537ZM54.8537 94.2295C56.2113 98.575 55.4117 103.461 52.4549 108.889C52.3217 109.155 52.1884 109.422 52.0434 109.677C54.7169 109.442 57.3216 108.702 59.7196 107.496C61.6934 106.384 63.5201 105.029 65.1568 103.462L68.1004 100.615C72.369 101.749 76.7685 102.315 81.1851 102.299C99.9992 102.299 115.252 92.5098 115.252 80.2972C115.252 68.0847 99.9992 58.2283 81.1851 58.2283C62.371 58.2283 47.1264 68.1465 47.1264 80.261C47.1264 85.5671 50.005 90.4383 54.8559 94.2273L54.8537 94.2295Z" fill="white"/>
|
||||
<path d="M64.8632 85.5074C63.8532 85.525 62.8609 85.2416 62.0125 84.6932C61.1641 84.1449 60.4982 83.3565 60.0994 82.4284C59.7006 81.5004 59.587 80.4746 59.7732 79.4818C59.9593 78.4889 60.4368 77.574 61.1447 76.8534C61.8526 76.1328 62.7589 75.6392 63.7483 75.4355C64.7377 75.2317 65.7653 75.327 66.7003 75.7093C67.6353 76.0915 68.4354 76.7434 68.9987 77.5819C69.562 78.4204 69.863 79.4076 69.8634 80.4177C69.8635 81.7526 69.3391 83.0342 68.4032 83.9861C67.4673 84.938 66.1947 85.484 64.86 85.5063L64.8632 85.5074ZM80.9885 85.5074C79.9786 85.5243 78.9865 85.2404 78.1386 84.6917C77.2907 84.1429 76.6252 83.3543 76.2269 82.4262C75.8286 81.498 75.7155 80.4723 75.902 79.4797C76.0886 78.4871 76.5662 77.5724 77.2743 76.8522C77.9823 76.1319 78.8887 75.6386 79.878 75.4352C80.8673 75.2317 81.8947 75.3272 82.8296 75.7095C83.7644 76.0919 84.5643 76.7438 85.1274 77.5822C85.6906 78.4206 85.9915 79.4077 85.9918 80.4177C85.992 81.7526 85.4676 83.0342 84.5317 83.9861C83.5957 84.938 82.3232 85.484 80.9885 85.5063V85.5074ZM97.1009 85.5074C95.9321 85.5158 94.7965 85.1186 93.8876 84.3835C92.9788 83.6485 92.3529 82.621 92.1166 81.4762C91.8804 80.3315 92.0484 79.1402 92.592 78.1054C93.1356 77.0706 94.0212 76.2563 95.0979 75.8013C96.1746 75.3462 97.3758 75.2786 98.4968 75.6099C99.6177 75.9412 100.589 76.6509 101.245 77.6181C101.902 78.5854 102.203 79.7503 102.096 80.9143C101.99 82.0784 101.483 83.1696 100.663 84.002C99.7197 84.9527 98.44 85.4935 97.1009 85.5074V85.5074Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 3 KiB |
10
public/assets/zulip.svg
Normal file
10
public/assets/zulip.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="160" height="160" fill="url(#paint0_linear_601_18)"/>
|
||||
<path d="M117 51.8124C117 55.7919 115.21 59.3278 112.481 61.4728L85.9949 85.087C85.503 85.5079 84.8635 84.8292 85.2184 84.2627L94.9338 64.8437C95.2061 64.3 94.853 63.6318 94.2943 63.6318H56.6114C50.7751 63.6318 46 58.314 46 51.8159C46 45.316 50.7751 40 56.6114 40H106.389C112.225 39.9965 117 45.3143 117 51.8124ZM56.6114 119H106.389C112.225 119 117 113.682 117 107.184C117 100.684 112.225 95.3682 106.389 95.3682H68.7057C68.147 95.3682 67.7939 94.7 68.0662 94.1563L77.7816 74.7373C78.1365 74.1708 77.497 73.4921 77.0051 73.913L50.5186 97.5237C47.7902 99.667 46 103.205 46 107.184C46 113.682 50.7751 119 56.6114 119Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_601_18" x1="9.5" y1="9.97447e-08" x2="143" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4BB4CC"/>
|
||||
<stop offset="1" stop-color="#25B097"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
|
@ -1,6 +1,6 @@
|
|||
# Rocketchat
|
||||
# Zulip
|
||||
|
||||
![Rocketchat](/assets/rocketchat.svg 'Rocketchat')
|
||||
![Zulip](/assets/zulip.svg 'Zulip')
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel felis rutrum, congue orci non, dictum
|
||||
augue. In hac habitasse platea dictumst. Donec enim neque, vehicula vel consequat non, facilisis sed mauris.
|
54
src/App.tsx
54
src/App.tsx
|
@ -1,36 +1,16 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Router, RouteComponentProps } from '@reach/router';
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
|
||||
import { isValid } from 'src/services/api';
|
||||
import { useAuth } from 'src/services/auth';
|
||||
import { Apps, Dashboard, Users, Login, AppSingle } from './modules';
|
||||
import { Dashboard, Users, Login } from './modules';
|
||||
import { Layout } from './components';
|
||||
import { LoginCallback } from './modules/login/LoginCallback';
|
||||
|
||||
type AppProps = RouteComponentProps;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function App(_: AppProps) {
|
||||
const { auth } = useAuth();
|
||||
// const isLoginPage = useMatch('/login');
|
||||
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const [initializedToken, setInitializedToken] = useState<string | null>(null);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!isValid(auth) && !isLoginPage) {
|
||||
// navigate('/login');
|
||||
// }
|
||||
// }, [auth, isLoginPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isValid(auth) && (!initialized || initializedToken !== auth.token)) {
|
||||
setInitialized(true);
|
||||
setInitializedToken(auth.token);
|
||||
}
|
||||
}, [auth, initialized, initializedToken]);
|
||||
function App() {
|
||||
const { authToken } = useAuth();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -46,24 +26,22 @@ function App(_: AppProps) {
|
|||
</Helmet>
|
||||
|
||||
<div className="app bg-gray-50 min-h-screen flex flex-col">
|
||||
{!isValid(auth) ? (
|
||||
<Router>
|
||||
<Login default path="/login" />
|
||||
<LoginCallback path="/login-callback" />
|
||||
</Router>
|
||||
{!authToken ? (
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/login-callback" element={<LoginCallback />} />
|
||||
<Route path="*" element={<Navigate to="/login" />} />
|
||||
</Routes>
|
||||
) : (
|
||||
<Layout>
|
||||
<Router>
|
||||
<Dashboard default path="/dashboard" />
|
||||
<Users path="/users" />
|
||||
<Apps path="/apps" />
|
||||
<AppSingle path="/apps/:id" />
|
||||
</Router>
|
||||
<Routes>
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/users" element={<Users />} />
|
||||
<Route path="*" element={<Navigate to="/dashboard" />} />
|
||||
</Routes>
|
||||
</Layout>
|
||||
)}
|
||||
|
||||
{/* {isValid(auth) ? <Redirect from="/" to="/dashboard" noThrow /> : <Redirect from="/" to="/" noThrow />} */}
|
||||
|
||||
{/* Place to load notifications */}
|
||||
<div
|
||||
aria-live="assertive"
|
||||
|
|
29
src/components/Banner/Banner.tsx
Normal file
29
src/components/Banner/Banner.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import { SpeakerphoneIcon } from '@heroicons/react/outline';
|
||||
|
||||
type BannerProps = {
|
||||
title: string;
|
||||
titleSm: string;
|
||||
};
|
||||
|
||||
const Banner: React.FC<BannerProps> = ({ title, titleSm }) => {
|
||||
return (
|
||||
<div className="bg-primary-600 rounded-md">
|
||||
<div className="max-w-7xl mx-auto py-3 px-3 sm:px-3">
|
||||
<div className="flex items-center justify-between flex-wrap">
|
||||
<div className="w-0 flex-1 flex items-center">
|
||||
<span className="flex p-2 rounded-lg bg-primary-800">
|
||||
<SpeakerphoneIcon className="h-6 w-6 text-white" aria-hidden="true" />
|
||||
</span>
|
||||
<p className="ml-3 font-small text-white truncate">
|
||||
<span className="md:hidden">{titleSm}</span>
|
||||
<span className="hidden md:inline">{title}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Banner;
|
1
src/components/Banner/index.ts
Normal file
1
src/components/Banner/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as Banner } from './Banner';
|
|
@ -1,12 +1,13 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import { Disclosure, Menu, Transition } from '@headlessui/react';
|
||||
import { MenuIcon, XIcon } from '@heroicons/react/outline';
|
||||
import { Link, RouteComponentProps } from '@reach/router';
|
||||
import { useAuth } from 'src/services/auth';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Dashboard', to: '/dashboard' },
|
||||
{ name: 'Apps', to: '/apps' },
|
||||
{ name: 'Users', to: '/users' },
|
||||
];
|
||||
|
||||
|
@ -14,15 +15,18 @@ function classNames(...classes: any[]) {
|
|||
return classes.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
type HeaderProps = RouteComponentProps;
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface HeaderProps {}
|
||||
|
||||
const Header: React.FC<HeaderProps> = () => {
|
||||
const { logOut } = useAuth();
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
return (
|
||||
<Disclosure as="nav" className="bg-white shadow">
|
||||
<Disclosure as="nav" className="bg-white shadow relative z-10">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div className="relative">
|
||||
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
|
||||
<div className="relative flex justify-between h-16">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
|
||||
|
@ -47,16 +51,13 @@ const Header: React.FC<HeaderProps> = () => {
|
|||
<Link
|
||||
key={item.name}
|
||||
to={item.to}
|
||||
getProps={({ isCurrent }) => {
|
||||
// the object returned here is passed to the
|
||||
// anchor element's props
|
||||
return {
|
||||
className: isCurrent
|
||||
? 'border-primary-400 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium'
|
||||
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium',
|
||||
'aria-current': isCurrent ? 'page' : undefined,
|
||||
};
|
||||
}}
|
||||
className={clsx(
|
||||
'border-primary-50 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium',
|
||||
{
|
||||
'border-primary-500 text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 text-sm font-medium':
|
||||
pathname.includes(item.to),
|
||||
},
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
|
@ -69,11 +70,9 @@ const Header: React.FC<HeaderProps> = () => {
|
|||
<div>
|
||||
<Menu.Button className="bg-white rounded-full flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
|
||||
<span className="sr-only">Open user menu</span>
|
||||
<img
|
||||
className="h-8 w-8 rounded-full"
|
||||
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt=""
|
||||
/>
|
||||
<span className="inline-flex items-center justify-center h-8 w-8 rounded-full bg-gray-500 overflow-hidden">
|
||||
<Gravatar email="" size={32} rating="pg" protocol="https://" />
|
||||
</span>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
|
@ -90,7 +89,23 @@ const Header: React.FC<HeaderProps> = () => {
|
|||
{({ active }) => (
|
||||
<a
|
||||
onClick={() => logOut()}
|
||||
className={classNames(active ? 'bg-gray-100' : '', 'block px-4 py-2 text-sm text-gray-700')}
|
||||
className={classNames(
|
||||
active ? 'bg-gray-100 cursor-pointer' : '',
|
||||
'block px-4 py-2 text-sm text-gray-700 cursor-pointer',
|
||||
)}
|
||||
>
|
||||
Configure profile
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
onClick={() => logOut()}
|
||||
className={classNames(
|
||||
active ? 'bg-gray-100 cursor-pointer' : '',
|
||||
'block px-4 py-2 text-sm text-gray-700 cursor-pointer',
|
||||
)}
|
||||
>
|
||||
Sign out
|
||||
</a>
|
||||
|
@ -109,23 +124,23 @@ const Header: React.FC<HeaderProps> = () => {
|
|||
<Link
|
||||
key={item.name}
|
||||
to={item.to}
|
||||
getProps={({ isCurrent }) => {
|
||||
// the object returned here is passed to the
|
||||
// anchor element's props
|
||||
return {
|
||||
className: isCurrent
|
||||
? 'bg-primary-50 border-primary-400 text-primary-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium'
|
||||
: 'border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium',
|
||||
'aria-current': isCurrent ? 'page' : undefined,
|
||||
};
|
||||
}}
|
||||
// getProps={({ isCurrent }) => {
|
||||
// // the object returned here is passed to the
|
||||
// // anchor element's props
|
||||
// return {
|
||||
// className: isCurrent
|
||||
// ? 'bg-primary-50 border-primary-400 text-primary-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium'
|
||||
// : 'border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium',
|
||||
// 'aria-current': isCurrent ? 'page' : undefined,
|
||||
// };
|
||||
// }}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import React from 'react';
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import { Header } from '../Header';
|
||||
|
||||
type DashboardProps = RouteComponentProps;
|
||||
|
||||
const Layout: React.FC<DashboardProps> = ({ children }) => {
|
||||
const Layout: React.FC = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export { Layout } from './Layout';
|
||||
export { Header } from './Header';
|
||||
export { Table } from './Table';
|
||||
export { Banner } from './Banner';
|
||||
export { Tabs } from './Tabs';
|
||||
export { Modal, ConfirmationModal } from './Modal';
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import axios from 'axios';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from '@reach/router';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
|
||||
import { isValid } from './services/api';
|
||||
|
@ -43,9 +43,9 @@ ReactDOM.render(
|
|||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<Router>
|
||||
<App path="*" />
|
||||
</Router>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
</React.StrictMode>,
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import React from 'react';
|
||||
import { Link, RouteComponentProps, RouterProps } from '@reach/router';
|
||||
import { ChevronRightIcon } from '@heroicons/react/solid';
|
||||
import { XCircleIcon } from '@heroicons/react/outline';
|
||||
import { Tabs } from 'src/components';
|
||||
import { Tabs, Banner } from 'src/components';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AdvancedTab, GeneralTab } from './components';
|
||||
|
||||
type AppSingleProps = RouteComponentProps & RouterProps;
|
||||
|
||||
const pages = [
|
||||
{ name: 'Apps', to: '/apps', current: true },
|
||||
{ name: 'Nextcloud', to: '', current: false },
|
||||
|
@ -17,7 +15,7 @@ const tabs = [
|
|||
{ name: 'Advanced Configuration', component: <AdvancedTab /> },
|
||||
];
|
||||
|
||||
export const AppSingle: React.FC<AppSingleProps> = () => {
|
||||
export const AppSingle: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 flex-grow">
|
||||
|
@ -48,7 +46,11 @@ export const AppSingle: React.FC<AppSingleProps> = () => {
|
|||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||
<Banner title="Managing single app instances coming soon." titleSm="Comming soon!" />
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 overflow-hidden opacity-40 cursor-default pointer-events-none select-none">
|
||||
<div className="bg-white overflow-hidden shadow rounded-sm mb-5">
|
||||
<div className="px-4 py-5 sm:p-6 flex justify-between items-center">
|
||||
<div className="mr-4 flex items-center">
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import { RouteComponentProps, navigate, Link } from '@reach/router';
|
||||
import { ChevronRightIcon, SearchIcon, PlusIcon } from '@heroicons/react/solid';
|
||||
import { CogIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { ConfirmationModal } from 'src/components/Modal';
|
||||
import { Table } from 'src/components';
|
||||
|
||||
type AppsProps = RouteComponentProps;
|
||||
import { Table, Banner } from 'src/components';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const pages = [{ name: 'Apps', href: '#', current: true }];
|
||||
|
||||
export const Apps: React.FC<AppsProps> = () => {
|
||||
export const Apps: React.FC = () => {
|
||||
const [selectedRowsIds, setSelectedRowsIds] = useState({});
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
|
@ -90,11 +88,11 @@ export const Apps: React.FC<AppsProps> = () => {
|
|||
},
|
||||
{
|
||||
Header: ' ',
|
||||
Cell: (e: any) => {
|
||||
Cell: () => {
|
||||
return (
|
||||
<div className="text-right opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={() => navigate(`/apps/${e.cell.row.original.id}`)}
|
||||
onClick={() => {}}
|
||||
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"
|
||||
>
|
||||
|
@ -115,115 +113,110 @@ export const Apps: React.FC<AppsProps> = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 flex-grow">
|
||||
<nav className="flex mb-8" aria-label="Breadcrumb">
|
||||
<ol className="flex items-center space-x-4">
|
||||
<li>
|
||||
<div className="flex items-center">
|
||||
<Link to="/dashboard" className="text-sm font-medium text-gray-500 hover:text-gray-700">
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
{pages.map((page) => (
|
||||
<li key={page.name}>
|
||||
<>
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 flex-grow">
|
||||
<nav className="flex" aria-label="Breadcrumb">
|
||||
<ol className="flex items-center space-x-4">
|
||||
<li>
|
||||
<div className="flex items-center">
|
||||
<ChevronRightIcon className="flex-shrink-0 h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
<a
|
||||
href={page.href}
|
||||
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
|
||||
aria-current={page.current ? 'page' : undefined}
|
||||
>
|
||||
{page.name}
|
||||
</a>
|
||||
<Link to="/dashboard" className="text-sm font-medium text-gray-500 hover:text-gray-700">
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div className="pb-5 border-b border-gray-200 sm:flex sm:items-center sm:justify-between">
|
||||
<h1 className="text-3xl leading-6 font-bold text-gray-900">Apps</h1>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<PlusIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
Add new app
|
||||
</button>
|
||||
</div>
|
||||
{pages.map((page) => (
|
||||
<li key={page.name}>
|
||||
<div className="flex items-center">
|
||||
<ChevronRightIcon className="flex-shrink-0 h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
<a
|
||||
href={page.href}
|
||||
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
|
||||
aria-current={page.current ? 'page' : undefined}
|
||||
>
|
||||
{page.name}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between w-100 my-3 items-center">
|
||||
<div className="flex items-center">
|
||||
{/* <div className="mr-3 inline-block">
|
||||
<label htmlFor="location" className="block text-sm font-medium text-gray-700 sr-only">
|
||||
Location
|
||||
</label>
|
||||
<select
|
||||
id="location"
|
||||
name="location"
|
||||
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-200 focus:outline-none focus:ring-primary-dark focus:border-primary-dark sm:text-sm rounded-md"
|
||||
defaultValue="All apps"
|
||||
>
|
||||
<option>All apps</option>
|
||||
<option>Owner</option>
|
||||
<option>Admins</option>
|
||||
<option>Members</option>
|
||||
</select>
|
||||
</div> */}
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 overflow-hidden">
|
||||
<Banner
|
||||
title="Your app instances management will be here soon, in the meantime, feel free to explore."
|
||||
titleSm="Comming soon!"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="inline-block">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 sr-only">
|
||||
Search candidates
|
||||
</label>
|
||||
<div className="mt-1 flex rounded-md shadow-sm">
|
||||
<div className="relative flex items-stretch flex-grow focus-within:z-10">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 flex-grow">
|
||||
<div className="pb-5 border-b border-gray-200 sm:flex sm:items-center sm:justify-between">
|
||||
<h1 className="text-3xl leading-6 font-bold text-gray-900">Apps</h1>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<button
|
||||
disabled
|
||||
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"
|
||||
>
|
||||
<PlusIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
Add new app
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between w-100 my-3 items-center">
|
||||
<div className="flex items-center">
|
||||
<div className="inline-block">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 sr-only">
|
||||
Search candidates
|
||||
</label>
|
||||
<div className="mt-1 flex rounded-md shadow-sm">
|
||||
<div className="relative flex items-stretch flex-grow focus-within:z-10">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
id="email"
|
||||
className="focus:ring-primary-dark focus:border-primary-dark block w-full rounded-md pl-10 sm:text-sm border-gray-200"
|
||||
placeholder="Search Apps"
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
id="email"
|
||||
className="focus:ring-primary-dark focus:border-primary-dark block w-full rounded-md pl-10 sm:text-sm border-gray-200"
|
||||
placeholder="Search Apps"
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedRowsIds && Object.keys(selectedRowsIds).length !== 0 && (
|
||||
<button
|
||||
onClick={deleteModalOpen}
|
||||
type="button"
|
||||
className="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-red-700 bg-red-50 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
<TrashIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
Delete
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="-my-2 sm:-mx-6 lg:-mx-8">
|
||||
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||
<div className="shadow border-b border-gray-200 sm:rounded-lg">
|
||||
<Table data={filterSearch} columns={columns} getSelectedRowIds={selectedRows} selectable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedRowsIds && Object.keys(selectedRowsIds).length !== 0 && (
|
||||
<button
|
||||
onClick={deleteModalOpen}
|
||||
type="button"
|
||||
className="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-red-700 bg-red-50 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
<TrashIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
Delete
|
||||
</button>
|
||||
)}
|
||||
<ConfirmationModal
|
||||
open={deleteModal}
|
||||
onClose={deleteModalClose}
|
||||
title="Delete service"
|
||||
body="Are you sure you want to delete this service? All of your data will be permanently removed. This action cannot be undone."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="-my-2 sm:-mx-6 lg:-mx-8">
|
||||
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||
<div className="shadow border-b border-gray-200 sm:rounded-lg">
|
||||
<Table data={filterSearch} columns={columns} getSelectedRowIds={selectedRows} selectable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmationModal
|
||||
open={deleteModal}
|
||||
onClose={deleteModalClose}
|
||||
title="Delete service"
|
||||
body="Are you sure you want to delete this service? All of your data will be permanently removed. This action cannot be undone."
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -98,21 +98,6 @@ export const Secrets = () => {
|
|||
name: 'nextcloud_password (admin login)',
|
||||
secret: `#B&>4A;O#XEF]_dE*zxF26>8fF:akgGL8gi#yALK{ZY[P$eE`,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'onlyoffice_jwt_secret',
|
||||
secret: `U),xxEbt*g~t[4:Jl6RU8Tih#3ExinoV|K?.gL/R%?;gzK)R`,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'onlyoffice_postgresql_password',
|
||||
secret: `U),xxEbt*g~t[4:Jl6RU8Tih#3ExinoV|K?.gL/R%?;gzK)R`,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'onlyoffice_rabbitmq_password',
|
||||
secret: `r7sFEUL&U(a;3yR;CaC7P,bPwUEKZOT=IyjJq%AWniY!ncP[`,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
|
|
@ -1,58 +1,54 @@
|
|||
import React from 'react';
|
||||
import { Link, RouteComponentProps } from '@reach/router';
|
||||
import clsx from 'clsx';
|
||||
import { DASHBOARD_APPS, DASHBOARD_QUICK_ACCESS } from './consts';
|
||||
import { DashboardCard } from './components';
|
||||
|
||||
export const Dashboard: React.FC<RouteComponentProps> = () => {
|
||||
export const Dashboard: React.FC = () => {
|
||||
const host = window.location.hostname;
|
||||
const rootDomain = host.split('.').shift();
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-4 px-3 sm:px-6 lg:px-8 h-full flex-grow">
|
||||
<nav className="flex mb-8" aria-label="Breadcrumb">
|
||||
<ol className="flex items-center space-x-4">
|
||||
<li>
|
||||
<div className="flex items-center">
|
||||
<a href="#" className="text-sm font-medium text-gray-500 hover:text-gray-700">
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div className="pb-5 border-b border-gray-200 sm:flex sm:items-center sm:justify-between">
|
||||
<h1 className="text-3xl leading-6 font-bold text-gray-900">Dashboard</h1>
|
||||
<div className="relative">
|
||||
<div className="max-w-7xl mx-auto py-4 px-3 sm:px-6 lg:px-8">
|
||||
<div className="mt-6 pb-5 border-b border-gray-200 sm:flex sm:items-center sm:justify-between">
|
||||
<h1 className="text-3xl leading-6 font-bold text-gray-900">Dashboard</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-4 lg:grid-cols-3 my-10">
|
||||
{DASHBOARD_APPS.map((app) => (
|
||||
<DashboardCard app={app} key={app.name} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<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">Quick access</h3>
|
||||
<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">
|
||||
{DASHBOARD_APPS(rootDomain!).map((app) => (
|
||||
<DashboardCard app={app} key={app.name} rootDomain={rootDomain} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<dl className="mt-5 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
{DASHBOARD_QUICK_ACCESS.map((item) => (
|
||||
<Link
|
||||
to={item.to}
|
||||
key={item.name}
|
||||
className={clsx('bg-white rounded-lg overflow-hidden sm:p-2 flex items-center group', {
|
||||
'opacity-40 cursor-default': !item.active,
|
||||
})}
|
||||
>
|
||||
<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 className="h-6 w-6 text-primary-900" aria-hidden="true" />
|
||||
</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">{item.description}</dd>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</dl>
|
||||
<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">Utilities</h3>
|
||||
</div>
|
||||
|
||||
<dl className="mt-5 grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
{DASHBOARD_QUICK_ACCESS(rootDomain!).map((item) => (
|
||||
<a
|
||||
href={item.to}
|
||||
key={item.name}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={clsx('bg-white rounded-lg overflow-hidden sm:p-2 flex items-center group', {
|
||||
'opacity-40 cursor-default': !item.active,
|
||||
})}
|
||||
>
|
||||
<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 className="h-6 w-6 text-primary-900" aria-hidden="true" />
|
||||
</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">{item.description}</dd>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { navigate } from '@reach/router';
|
||||
import { Modal } from 'src/components';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export const DashboardCard: React.FC<any> = ({ app }: { app: any }) => {
|
||||
export const DashboardCard: React.FC<any> = ({ app, rootDomain }: { app: any; rootDomain: string }) => {
|
||||
const [readMoreVisible, setReadMoreVisible] = useState(false);
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
const onReadMoreClick = () => setReadMoreVisible(true);
|
||||
const onReadMoreCloseClick = () => setReadMoreVisible(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -24,29 +23,25 @@ export const DashboardCard: React.FC<any> = ({ app }: { app: any }) => {
|
|||
<div className="bg-white overflow-hidden shadow rounded-lg divide-y divide-gray-100 mb-4 md:mb-0" key={app.name}>
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<div className="mr-4 flex items-center">
|
||||
<img className="h-16 w-16 rounded-md overflow-hidden mr-4" src={app.assetSrc} alt="Nextcloud" />
|
||||
<img
|
||||
className="h-16 w-16 rounded-md overflow-hidden mr-4 flex-shrink-0"
|
||||
src={app.assetSrc}
|
||||
alt="Nextcloud"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<h2 className="text-2xl leading-8 font-bold">{app.name}</h2>
|
||||
<div className="text-sm leading-5 font-medium text-gray-500">Installed on August 25, 2020</div>
|
||||
<h2 className="text-xl leading-8 font-bold">{app.name}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2.5 py-2.5 sm:px-4 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onReadMoreClick}
|
||||
className="mr-3 inline-flex items-center px-2.5 py-1.5 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 justify-center"
|
||||
>
|
||||
Read more
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigate(`/apps/${app.id}`)}
|
||||
type="button"
|
||||
<Link
|
||||
to={`${app.urlShorthand}.${rootDomain}`}
|
||||
target="_blank"
|
||||
className="inline-flex items-center px-2.5 py-1.5 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
||||
>
|
||||
Configure
|
||||
</button>
|
||||
Launch App
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,104 +1,51 @@
|
|||
import {
|
||||
ChartBarIcon,
|
||||
CloudIcon,
|
||||
UsersIcon,
|
||||
CogIcon,
|
||||
SwitchHorizontalIcon,
|
||||
ClockIcon,
|
||||
ShieldCheckIcon,
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import { ChartBarIcon, InformationCircleIcon } from '@heroicons/react/outline';
|
||||
|
||||
export const DASHBOARD_APPS = [
|
||||
export const DASHBOARD_APPS = (rootDomain: string) => [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Nextcloud',
|
||||
assetSrc: '/assets/nextcloud.svg',
|
||||
markdownSrc: '/markdown/nextcloud.md',
|
||||
url: `files.${rootDomain}`,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Wekan',
|
||||
assetSrc: '/assets/wekan.svg',
|
||||
markdownSrc: '/markdown/wekan.md',
|
||||
url: `wekan.${rootDomain}`,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Rocketchat',
|
||||
assetSrc: '/assets/rocketchat.svg',
|
||||
markdownSrc: '/markdown/rocket.md',
|
||||
name: 'Zulip',
|
||||
assetSrc: '/assets/zulip.svg',
|
||||
markdownSrc: '/markdown/zulip.md',
|
||||
url: `zulip.${rootDomain}`,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Wordpress',
|
||||
assetSrc: '/assets/wordpress.svg',
|
||||
markdownSrc: '/markdown/wordpress.md',
|
||||
url: `www.${rootDomain}`,
|
||||
},
|
||||
];
|
||||
|
||||
export const DASHBOARD_QUICK_ACCESS = [
|
||||
export const DASHBOARD_QUICK_ACCESS = (rootDomain: string) => [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Users →',
|
||||
to: '/users',
|
||||
description: 'Manage users and their permissions',
|
||||
icon: UsersIcon,
|
||||
name: 'Monitoring →',
|
||||
to: `grafana.${rootDomain}`,
|
||||
description: 'Monitor your system with Grafana',
|
||||
icon: ChartBarIcon,
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Apps →',
|
||||
to: '/apps',
|
||||
description: 'Stay on top of your deadlines, or don’t — it’s up to you',
|
||||
icon: CloudIcon,
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Monitoring →',
|
||||
to: '/dashboard',
|
||||
description: 'Monitor your system with Grafana',
|
||||
icon: ChartBarIcon,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Data Migration →',
|
||||
to: '/dashboard',
|
||||
description: 'Import your data from other platforms and services',
|
||||
icon: SwitchHorizontalIcon,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Account Settings →',
|
||||
to: '/dashboard',
|
||||
description: 'Manage your organisation’s profile and preferences',
|
||||
icon: CogIcon,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Backup & Restore →',
|
||||
to: '/dashboard',
|
||||
description: 'Backup or restore your data',
|
||||
icon: ClockIcon,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'Security →',
|
||||
to: '/dashboard',
|
||||
description: 'Configure security settings, view alerts and analytics',
|
||||
icon: ShieldCheckIcon,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'Support →',
|
||||
to: '/dashboard',
|
||||
to: 'https://docs.stackspin.net',
|
||||
description: 'Access documentation and forum',
|
||||
icon: InformationCircleIcon,
|
||||
active: false,
|
||||
active: true,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import React from 'react';
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import clsx from 'clsx';
|
||||
import { LockClosedIcon } from '@heroicons/react/solid';
|
||||
|
||||
import { performApiCall } from 'src/services/api';
|
||||
import { showToast, ToastType } from 'src/common/util/show-toast';
|
||||
|
||||
type LoginProps = RouteComponentProps;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function Login(_: LoginProps) {
|
||||
export function Login() {
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const { data } = await performApiCall({
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { navigate, RouteComponentProps } from '@reach/router';
|
||||
import { useAuth } from 'src/services/auth';
|
||||
import { showToast, ToastType } from 'src/common/util/show-toast';
|
||||
|
||||
type LoginCallbackProps = RouteComponentProps;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function LoginCallback(_: LoginCallbackProps) {
|
||||
export function LoginCallback() {
|
||||
const currentURL = window.location.href;
|
||||
const indexOfQuestionMark = currentURL.indexOf('?');
|
||||
const params = currentURL.slice(indexOfQuestionMark);
|
||||
|
@ -15,16 +11,10 @@ export function LoginCallback(_: LoginCallbackProps) {
|
|||
|
||||
useEffect(() => {
|
||||
if (params.length > 2) {
|
||||
const res = logIn(params);
|
||||
// @ts-ignore
|
||||
if (res.ok) {
|
||||
showToast('Logged in');
|
||||
} else {
|
||||
showToast('Something went wrong', ToastType.Error);
|
||||
navigate('/login');
|
||||
}
|
||||
logIn(params);
|
||||
}
|
||||
}, [logIn, params]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [params]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import { RouteComponentProps, Link } from '@reach/router';
|
||||
import { ChevronRightIcon, SearchIcon, PlusIcon } from '@heroicons/react/solid';
|
||||
import { SearchIcon, PlusIcon } from '@heroicons/react/solid';
|
||||
import { CogIcon, TrashIcon } from '@heroicons/react/outline';
|
||||
import { useUsers } from 'src/services/users';
|
||||
import { Table } from 'src/components';
|
||||
|
@ -9,9 +8,7 @@ import { ConfirmationModal } from 'src/components/Modal';
|
|||
import { debounce } from 'lodash';
|
||||
import { UserModal } from './components/UserModal';
|
||||
|
||||
const pages = [{ name: 'Users', href: '#', current: true }];
|
||||
|
||||
export const Users: React.FC<RouteComponentProps> = () => {
|
||||
export const Users: React.FC = () => {
|
||||
const [selectedRowsIds, setSelectedRowsIds] = useState({});
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
const [configureModal, setConfigureModal] = useState(false);
|
||||
|
@ -92,103 +89,79 @@ export const Users: React.FC<RouteComponentProps> = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 h-full flex-grow">
|
||||
<nav className="flex mb-8" aria-label="Breadcrumb">
|
||||
<ol className="flex items-center space-x-4">
|
||||
<li>
|
||||
<div className="flex items-center">
|
||||
<Link to="/dashboard" className="text-sm font-medium text-gray-500 hover:text-gray-700">
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
{pages.map((page) => (
|
||||
<li key={page.name}>
|
||||
<div className="flex items-center">
|
||||
<ChevronRightIcon className="flex-shrink-0 h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
<a
|
||||
href={page.href}
|
||||
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
|
||||
aria-current={page.current ? 'page' : undefined}
|
||||
>
|
||||
{page.name}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div className="pb-5 border-b border-gray-200 sm:flex sm:items-center sm:justify-between">
|
||||
<h1 className="text-3xl leading-6 font-bold text-gray-900">Users</h1>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<PlusIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
Add new user
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between w-100 my-3 items-center mb-5 ">
|
||||
<div className="flex items-center">
|
||||
<div className="inline-block">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 sr-only">
|
||||
Search candidates
|
||||
</label>
|
||||
<div className="mt-1 flex rounded-md shadow-sm">
|
||||
<div className="relative flex items-stretch flex-grow focus-within:z-10">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
id="email"
|
||||
className="focus:ring-primary-500 focus:border-primary-500 block w-full rounded-md pl-10 sm:text-sm border-gray-200"
|
||||
placeholder="Search Users"
|
||||
onChange={debouncedSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedRowsIds && Object.keys(selectedRowsIds).length !== 0 && (
|
||||
<div className="flex items-center">
|
||||
<div className="relative">
|
||||
<div className="max-w-7xl mx-auto py-4 sm:px-6 lg:px-8 h-full flex-grow">
|
||||
<div className="pb-5 mt-6 border-b border-gray-200 sm:flex sm:items-center sm:justify-between">
|
||||
<h1 className="text-3xl leading-6 font-bold text-gray-900">Users</h1>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<button
|
||||
onClick={deleteModalOpen}
|
||||
onClick={() => configureModalOpen(null)}
|
||||
type="button"
|
||||
className="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-red-700 bg-red-50 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
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"
|
||||
>
|
||||
<TrashIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
Delete
|
||||
<PlusIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
Add new user
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="-my-2 sm:-mx-6 lg:-mx-8">
|
||||
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||
<div className="shadow border-b border-gray-200 sm:rounded-lg">
|
||||
<Table data={filterSearch as any} columns={columns} getSelectedRowIds={selectedRows} selectable />
|
||||
<div className="flex justify-between w-100 my-3 items-center mb-5 ">
|
||||
<div className="flex items-center">
|
||||
<div className="inline-block">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 sr-only">
|
||||
Search candidates
|
||||
</label>
|
||||
<div className="mt-1 flex rounded-md shadow-sm">
|
||||
<div className="relative flex items-stretch flex-grow focus-within:z-10">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
id="email"
|
||||
className="focus:ring-primary-500 focus:border-primary-500 block w-full rounded-md pl-10 sm:text-sm border-gray-200"
|
||||
placeholder="Search Users"
|
||||
onChange={debouncedSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedRowsIds && Object.keys(selectedRowsIds).length !== 0 && (
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={deleteModalOpen}
|
||||
type="button"
|
||||
className="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-red-700 bg-red-50 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
<TrashIcon className="-ml-0.5 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="-my-2 sm:-mx-6 lg:-mx-8">
|
||||
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||
<div className="shadow border-b border-gray-200 sm:rounded-lg">
|
||||
<Table data={filterSearch as any} columns={columns} getSelectedRowIds={selectedRows} selectable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmationModal
|
||||
open={deleteModal}
|
||||
onClose={deleteModalClose}
|
||||
title="Delete user"
|
||||
body="Are you sure you want to delete this user? All of your data will be permanently removed. This action cannot be undone."
|
||||
/>
|
||||
|
||||
<UserModal open={configureModal} onClose={configureModalClose} userId={userId} />
|
||||
</div>
|
||||
|
||||
<ConfirmationModal
|
||||
open={deleteModal}
|
||||
onClose={deleteModalClose}
|
||||
title="Delete user"
|
||||
body="Are you sure you want to delete this user? All of your data will be permanently removed. This action cannot be undone."
|
||||
/>
|
||||
|
||||
<UserModal open={configureModal} onClose={configureModalClose} userId={userId} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { TrashIcon } from '@heroicons/react/outline';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Modal } from 'src/components';
|
||||
import { Modal, Banner } from 'src/components';
|
||||
import { Input } from 'src/components/Form';
|
||||
import { useUsers } from 'src/services/users';
|
||||
import { CurrentUserState } from 'src/services/users/redux';
|
||||
|
@ -95,7 +95,7 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
|
|||
<div className="space-y-4 divide-y divide-gray-200">
|
||||
<div>
|
||||
<div>
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">Personal Information</h3>
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">{userId ? 'Edit user' : 'Add new user'}</h3>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
|
||||
|
@ -107,7 +107,11 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
|
|||
<Input control={control} name="email" label="Email" type="email" onKeyPress={handleKeyPress} />
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-3">
|
||||
<div className="sm:col-span-6">
|
||||
<Banner title="Editing user status, roles and app access coming soon." titleSm="Comming soon!" />
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
||||
{/* <Select control={control} name="status" label="Status" options={['Active', 'Inactive']} /> */}
|
||||
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
|
||||
Status
|
||||
|
@ -125,7 +129,7 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-3">
|
||||
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
||||
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
|
||||
Role
|
||||
</label>
|
||||
|
@ -144,7 +148,7 @@ export const UserModal = ({ open, onClose, userId }: UserModalProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="opacity-40 cursor-default pointer-events-none select-none">
|
||||
<div className="mt-4">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">App Access</h3>
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,7 @@ export const appAccessList = [
|
|||
name: 'NextCloud',
|
||||
},
|
||||
{
|
||||
image: '/assets/rocketchat.svg',
|
||||
name: 'RocketChat',
|
||||
image: '/assets/zulip.svg',
|
||||
name: 'Zulip',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getAuth, signIn, signOut } from '../redux';
|
||||
import { getAuthToken, signIn, signOut } from '../redux';
|
||||
|
||||
export function useAuth() {
|
||||
const dispatch = useDispatch();
|
||||
const auth = useSelector(getAuth);
|
||||
const authToken = useSelector(getAuthToken);
|
||||
|
||||
const logIn = useCallback(
|
||||
(params) => {
|
||||
|
@ -18,7 +18,7 @@ export function useAuth() {
|
|||
}, [dispatch]);
|
||||
|
||||
return {
|
||||
auth,
|
||||
authToken,
|
||||
logIn,
|
||||
logOut,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { signIn, signOut, AuthActionTypes } from './actions';
|
||||
export { default as reducer } from './reducers';
|
||||
export { getAuth, getIsAuthLoading } from './selectors';
|
||||
export { getAuth, getIsAuthLoading, getAuthToken } from './selectors';
|
||||
export * from './types';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createApiReducer, chainReducers, INITIAL_API_STATUS, INITIAL_API_STATE } from 'src/services/api';
|
||||
import { createApiReducer, chainReducers, INITIAL_API_STATUS } from 'src/services/api';
|
||||
|
||||
import { AuthState } from './types';
|
||||
import { AuthActionTypes } from './actions';
|
||||
|
@ -16,8 +16,8 @@ const auth = createApiReducer(
|
|||
|
||||
const signOut = createApiReducer(
|
||||
['', AuthActionTypes.SIGN_OUT, ''],
|
||||
() => INITIAL_API_STATE,
|
||||
() => INITIAL_API_STATE,
|
||||
() => initialState,
|
||||
() => initialState,
|
||||
);
|
||||
|
||||
export default chainReducers(initialState, auth, signOut);
|
||||
|
|
|
@ -4,6 +4,8 @@ import { isLoading } from 'src/services/api';
|
|||
|
||||
export const getAuth = (state: State) => state.auth;
|
||||
|
||||
export const getAuthToken = (state: State) => state.auth.token;
|
||||
|
||||
export const getIsAuthLoading = (state: State) => isLoading(getAuth(state));
|
||||
|
||||
export const getToken = (state: State) => state.auth.token;
|
||||
|
|
Loading…
Reference in a new issue