initial commit

This commit is contained in:
braginini
2021-09-03 15:00:54 +02:00
commit 99fa0fb4ac
53 changed files with 39872 additions and 0 deletions

86
src/App.js Normal file
View File

@@ -0,0 +1,86 @@
import React, {useEffect, useState} from 'react';
import Navbar from './components/Navbar';
import {Redirect, Route, Switch} from 'react-router-dom';
import Peers from './views/Peers';
import Footer from './components/Footer';
import {useAuth0} from "@auth0/auth0-react";
import Loading from "./components/Loading";
import SetupKeys from "./views/SetupKeys";
import AddPeer from "./views/AddPeer";
import AccessControl from "./views/AccessControl";
import Activity from "./views/Activity";
function App() {
const {
isLoading,
isAuthenticated,
loginWithRedirect,
error
} = useAuth0();
const [isOpen, setIsOpen] = useState(false);
const toggle = () => {
setIsOpen(!isOpen);
};
useEffect(() => {
const hideMenu = () => {
if (window.innerWidth > 768 && isOpen) {
setIsOpen(false);
console.log('i resized');
}
};
window.addEventListener('resize', hideMenu);
return () => {
window.removeEventListener('resize', hideMenu);
};
});
if (error) {
return <div>Oops... {error.message}</div>;
}
if (isLoading) {
return <Loading/>;
}
if (!isAuthenticated) {
loginWithRedirect({})
}
return (
isAuthenticated && (
<>
{/*<div className='h-screen flex justify-center items-center bg-green-400'>*/}
<Navbar toggle={toggle}/>
<div className="min-h-screen bg-white">
<Switch>
<Route
exact
path="/"
render={() => {
return (
<Redirect to="/peers"/>
)
}}
/>
<Route path='/peers' exact component={Peers}/>
<Route path="/add-peer" component={AddPeer}/>
<Route path="/setup-keys" component={SetupKeys}/>
<Route path="/acls" component={AccessControl}/>
<Route path="/activity" component={Activity}/>
</Switch>
</div>
<Footer/>
</>
)
);
}
export default App;

8
src/App.test.js Normal file
View File

@@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

55
src/api/ManagementAPI.js Normal file
View File

@@ -0,0 +1,55 @@
import {getConfig} from "../config";
const {apiOrigin} = getConfig();
export const callApi = async (method, headers, body, getAccessTokenSilently, endpoint) => {
const token = await getAccessTokenSilently();
if (!headers) {
headers = {}
}
headers.Authorization = `Bearer ${token}`
const requestOptions = {
method: method,
headers: headers,
body: body
};
const response = await fetch(`${apiOrigin}${endpoint}`, requestOptions);
return await response.json();
};
export const getSetupKeys = async (getAccessTokenSilently) => {
return callApi("GET", {}, null, getAccessTokenSilently, "/api/setup-keys")
}
export const revokeSetupKey = async (getAccessTokenSilently, keyId) => {
return callApi(
"PUT",
{'Content-Type': 'application/json'},
JSON.stringify({Revoked: true}),
getAccessTokenSilently,
"/api/setup-keys/" + keyId)
}
export const renameSetupKey = async (getAccessTokenSilently, keyId, newName) => {
return callApi(
"PUT",
{'Content-Type': 'application/json'},
JSON.stringify({Name: newName}),
getAccessTokenSilently,
"/api/setup-keys/" + keyId)
}
export const getPeers = async (getAccessTokenSilently) => {
return callApi("GET", {}, null, getAccessTokenSilently, "/api/peers")
}
export const deletePeer = async (getAccessTokenSilently, peerId) => {
return callApi(
"DELETE",
{},
null,
getAccessTokenSilently,
"/api/peers/" + peerId)
}

52
src/assets/bars.svg Normal file
View File

@@ -0,0 +1,52 @@
<svg width="135" height="140" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill-opacity="0.8">
<rect y="10" width="15" height="120" rx="6" fill="#4D4D4D">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="30" y="10" width="15" height="120" rx="6" fill="#4D4D4D">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="60" width="15" height="140" rx="6" fill="#FF6600">
<animate attributeName="height"
begin="0s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="90" y="10" width="15" height="120" rx="6" fill="#C8BEB7">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="120" y="10" width="15" height="120" rx="6" fill="#C8BEB7">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
src/assets/logo-full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

28
src/components/Content.js Normal file
View File

@@ -0,0 +1,28 @@
import React from 'react';
import ImageOne from '../images/egg.jpg';
import ImageTwo from '../images/egg-2.jpg';
const Content = () => {
return (
<>
<div className='menu-card'>
<img src={ImageOne} alt='egg' className='h-full rounded mb-20 shadow' />
<div className='center-content'>
<h2 className='text-2xl mb-2'>Egg Muffins</h2>
<p className='mb-2'>Cripsy, delicious, and nutritious</p>
<span>$16</span>
</div>
</div>
<div className='menu-card'>
<img src={ImageTwo} alt='egg' className='h-full rounded mb-20 shadow' />
<div className='center-content'>
<h2 className='text-2xl mb-2'>Egg Salad</h2>
<p className='mb-2'>Cripsy, delicious, and nutritious</p>
<span>$18</span>
</div>
</div>
</>
);
};
export default Content;

View File

@@ -0,0 +1,39 @@
import React from 'react';
const CopyButton = ({idPrefix, toCopy}) => {
const copyIconId = idPrefix + "copy"
const copySuccessIconId = idPrefix + "copy-success"
const classHidden = "hidden"
const handleKeyCopy = () => {
navigator.clipboard.writeText(toCopy)
let copyIcon = document.getElementById(copyIconId);
let copySuccessIcon = document.getElementById(copySuccessIconId);
copyIcon.classList.add(classHidden);
copySuccessIcon.classList.remove(classHidden);
setTimeout(function() {
copySuccessIcon.classList.add(classHidden);
copyIcon.classList.remove(classHidden);
}, 1200);
}
return (
<button
onClick={handleKeyCopy}
className="whitespace-nowrap font-medium text-gray-500 hover:text-gray-400">
<svg id={copyIconId} xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2"/>
</svg>
<svg id={copySuccessIconId} xmlns="http://www.w3.org/2000/svg" className="hidden h-6 w-6 text-green-500" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7"/>
</svg>
</button>
)
}
export default CopyButton;

View File

@@ -0,0 +1,36 @@
import React from 'react';
const CopyButton = ({idPrefix, text}) => {
const copyIconId = idPrefix + "copy"
const copySuccessIconId = idPrefix + "copy-success"
const classHidden = "hidden"
const handleKeyCopy = () => {
navigator.clipboard.writeText(text)
let copyIcon = document.getElementById(copyIconId);
let copySuccessIcon = document.getElementById(copySuccessIconId);
copyIcon.classList.add(classHidden);
copySuccessIcon.classList.remove(classHidden);
setTimeout(function() {
copySuccessIcon.classList.add(classHidden);
copyIcon.classList.remove(classHidden);
}, 1200);
}
return (
<button
onClick={handleKeyCopy}
className="whitespace-nowrap font-medium text-gray-500 hover:text-gray-400">
<div id={copyIconId}>
{text}
</div>
<div id={copySuccessIconId} className="flex flex-row hidden text-green-500">
Copied!
</div>
</button>
)
}
export default CopyButton;

View File

@@ -0,0 +1,103 @@
import {Fragment, useEffect, useRef, useState} from 'react'
import {Dialog, Transition} from '@headlessui/react'
import {ExclamationIcon} from '@heroicons/react/outline'
import PropTypes from "prop-types";
const DeleteDialog = ({show, text, title, confirmCallback}) => {
const [open, setOpen] = useState(show)
const cancelButtonRef = useRef(null)
useEffect(() => {
setOpen(show)
}, [show]);
return (
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="fixed z-10 inset-0 overflow-y-auto" initialFocus={cancelButtonRef}
onClose={() =>confirmCallback(false)}>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"/>
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div
className="inline-block align-bottom bg-white squared-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div
className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 squared-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationIcon className="h-6 w-6 text-red-600" aria-hidden="true"/>
</div>
<div className="font-mono mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<Dialog.Title as="h3" className="text-m leading-6 font-medium text-gray-900">
{title}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm font-mono text-gray-500">
{text}
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
className="font-mono w-full inline-flex justify-center squared-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
confirmCallback(true)
}}
>
Confirm
</button>
<button
type="button"
className="font-mono mt-3 w-full inline-flex justify-center squared-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
confirmCallback(false)
}}
ref={cancelButtonRef}
>
Cancel
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
)
}
DeleteDialog.propTypes = {
show: PropTypes.bool,
confirmCallback: PropTypes.func,
text: PropTypes.string
};
DeleteDialog.defaultProps = {};
export default DeleteDialog;

View File

@@ -0,0 +1,56 @@
import React, {Fragment} from 'react';
import {Menu, Transition} from "@headlessui/react";
import {Link} from "react-router-dom";
import {classNames} from "../utils/common";
const EditButton = ({items, handler}) => {
const handleAction = (action) => {
handler(action)
}
return (
<Menu as="div">
<div>
<Menu.Button
className="whitespace-nowrap font-medium text-gray-500 hover:text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z"/>
</svg>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="transform opacity-100 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="absolute mt-2 squared-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
{items.map((item, idx) => (
<Menu.Item key={item.name}>
{({active}) => (
<Link
to="#"
className={classNames(active ? 'bg-gray-100' : 'font-mono', 'block px-4 py-2 text-sm text-gray-700 font-mono')}
onClick={() => handleAction(item.name)}
>
{item.name}
</Link>
)}
</Menu.Item>
))}
</Menu.Items>
</Transition>
</Menu>
)
}
export default EditButton;

View File

@@ -0,0 +1,28 @@
import {Link} from 'react-router-dom'
export default function EmptyPeersPanel() {
return (
<Link
as="button"
to="/add-peer"
className="relative block w-full border-2 border-gray-300 border-dashed squared-lg p-12 text-center hover:border-gray-400 focus:outline-none"
>
<svg
className="mx-auto h-12 w-12 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
aria-hidden="true"
>
<path
strokeLinecap="square"
strokeLinejoin="square"
strokeWidth={2}
d="M8 14v20c0 4.418 7.163 8 16 8 1.381 0 2.721-.087 4-.252M8 14c0 4.418 7.163 8 16 8s16-3.582 16-8M8 14c0-4.418 7.163-8 16-8s16 3.582 16 8m0 0v14m0-4c0 4.418-7.163 8-16 8S8 28.418 8 24m32 10v6m0 0v6m0-6h6m-6 0h-6"
/>
</svg>
<span className="mt-2 block font-mono font-semibold text-sm font-medium text-gray-900">Let's get started by adding your first peer</span>
</Link>
)
}

13
src/components/Footer.js Normal file
View File

@@ -0,0 +1,13 @@
import React from 'react';
const Footer = () => {
return (
<div className='flex justify-center items-center h-24 bg-gray-100 text-gray'>
<p className="font-mono">
Copyright © 2021 <a className="underline text-blue-600 hover:text-blue-800 visited:text-purple-600 font-mono" href="https://wiretrustee.com">Wiretrustee Authors</a>
</p>
</div>
);
};
export default Footer;

34
src/components/Hero.js Normal file
View File

@@ -0,0 +1,34 @@
import React from 'react';
import { Link } from 'react-router-dom';
const Hero = () => {
return (
<div className='bg-white h-screen flex flex-col justify-center items-center'>
<h1 className='lg:text-9xl md:text-7xl sm:text-5xl text-3xl font-black mb-14'>
EGGCELLENT
</h1>
<Link
className='py-6 px-10 bg-yellow-500 rounded-full text-3xl hover:bg-yellow-300 transition duration-300 ease-in-out flex items-center animate-bounce'
to='/menu'
>
Order Now{' '}
<svg
className='w-6 h-6 ml-4'
fill='none'
stroke='currentColor'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d='M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z'
/>
</svg>
</Link>
</div>
);
};
export default Hero;

View File

@@ -0,0 +1,73 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import hljs from "highlight.js";
import "highlight.js/styles/monokai-sublime.css";
const registeredLanguages = {};
class Highlight extends Component {
constructor(props) {
super(props);
this.state = { loaded: false };
this.codeNode = React.createRef();
}
componentDidMount() {
const { language } = this.props;
if (language && !registeredLanguages[language]) {
try {
const newLanguage = require(`highlight.js/lib/languages/${language}`);
hljs.registerLanguage(language, newLanguage);
registeredLanguages[language] = true;
this.setState({ loaded: true }, this.highlight);
} catch (e) {
console.error(e);
throw Error(`Cannot register the language ${language}`);
}
} else {
this.setState({ loaded: true });
}
}
componentDidUpdate() {
this.highlight();
}
highlight = () => {
this.codeNode &&
this.codeNode.current &&
hljs.highlightElement(this.codeNode.current);
};
render() {
const { language, children } = this.props;
const { loaded } = this.state;
if (!loaded) {
return null;
}
return (
<pre className="rounded">
<code ref={this.codeNode} className={language}>
{children}
</code>
</pre>
);
}
}
Highlight.propTypes = {
children: PropTypes.node.isRequired,
language: PropTypes.string,
};
Highlight.defaultProps = {
language: "javascript",
};
export default Highlight;

13
src/components/Loading.js Normal file
View File

@@ -0,0 +1,13 @@
import React from "react";
import loading from "../assets/bars.svg";
const Loading = () => (
<div>
<div className="flex h-screen items-center justify-center" >
<img src={loading} alt="Loading" width="50" height="50"/>
</div>
</div>
);
export default Loading;

228
src/components/Navbar.js Normal file
View File

@@ -0,0 +1,228 @@
import React, {Fragment} from 'react';
import {Link, NavLink} from 'react-router-dom';
import logo from "../assets/logo.png";
import {Disclosure, Menu, Transition} from '@headlessui/react'
import {MenuIcon, XIcon} from '@heroicons/react/outline'
import {useAuth0} from "@auth0/auth0-react";
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
const Navbar = ({toggle}) => {
const {
user,
isAuthenticated,
logout,
} = useAuth0();
const logoutWithRedirect = () =>
logout({
returnTo: window.location.origin,
});
return (
<Disclosure as="nav" className="bg-gray-100 border-b border-gray-200">
{({open}) => (
<>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-24">
<div className="flex">
<div className="flex-shrink-0 flex items-center">
<Link to="/">
<img
className="block lg:hidden h-10 w-auto"
src={logo}
alt="Workflow"
/>
<img
className="hidden lg:block h-10 w-auto"
src={logo}
alt="Workflow"
/>
</Link>
</div>
<div className="hidden sm:ml-16 sm:flex sm:space-x-8">
{/* Current: "border-indigo-500 text-gray-900", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" */}
{isAuthenticated && (
<NavLink
to="/peers"
activeClassName="border-indigo-500 text-gray-900 border-b-2"
className="border-indigo-500 text-gray-600 inline-flex items-center px-1 pt-1 text-m font-medium font-mono"
>
Peers
</NavLink>
)}
{isAuthenticated && (
<NavLink
to="/add-peer"
activeClassName="border-indigo-500 text-gray-900 border-b-2"
className="border-indigo-500 text-gray-600 inline-flex items-center px-1 pt-1 text-m font-medium font-mono"
>
Add Peer
</NavLink>
)}
{isAuthenticated && (
<NavLink
to="/setup-keys"
activeClassName="border-indigo-500 text-gray-900 border-b-2"
className="border-indigo-500 text-gray-600 inline-flex items-center px-1 pt-1 text-m font-medium font-mono"
>
Setup Keys
</NavLink>
)}
{isAuthenticated && (
<NavLink
to="/acls"
activeClassName="border-indigo-500 text-gray-900 border-b-2"
className="border-indigo-500 text-gray-600 inline-flex items-center px-1 pt-1 text-m font-medium font-mono"
>
Access Control
</NavLink>
)}
{isAuthenticated && (
<NavLink
to="/activity"
activeClassName="border-indigo-500 text-gray-900 border-b-2"
className="border-indigo-500 text-gray-600 inline-flex items-center px-1 pt-1 text-m font-medium font-mono"
>
Activity
</NavLink>
)}
</div>
</div>
<div className="hidden sm:ml-6 sm:flex sm:items-center">
<Menu as="div" className="ml-3 relative">
<div>
<Menu.Button
className="bg-white rounded-full flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<span className="sr-only">Open user menu</span>
<img
className="h-12 w-auto rounded-full"
src={user.picture}
alt=""
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="origin-top-right absolute right-0 mt-2 w-48 squared-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
<Menu.Item>
{({active}) => (
<NavLink
to="#"
id="qsLogoutBtn"
className={classNames(active ? 'bg-gray-100' : 'font-mono', 'block px-4 py-2 text-sm text-gray-700 font-mono')}
onClick={() => logoutWithRedirect()}
>
Sign out
</NavLink>
)}
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
</div>
<div className="-mr-2 flex items-center sm:hidden">
{/* Mobile menu button */}
<Disclosure.Button
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
<span className="sr-only">Open main menu</span>
{open ? (
<XIcon className="block h-6 w-6" aria-hidden="true"/>
) : (
<MenuIcon className="block h-6 w-6" aria-hidden="true"/>
)}
</Disclosure.Button>
</div>
</div>
</div>
<Disclosure.Panel className="sm:hidden">
<div className="pt-2 pb-3 space-y-1">
{isAuthenticated && (
<Link
to="/peers"
className="block px-4 py-2 text-base font-medium font-mono text-gray-500 hover:text-gray-800 hover:bg-gray-100"
>
Peers
</Link>
)}
{isAuthenticated && (
<Link
to="/add-peer"
className="block px-4 py-2 text-base font-medium font-mono text-gray-500 hover:text-gray-800 hover:bg-gray-100"
>
Add Peer
</Link>
)}
{isAuthenticated && (
<Link
to="/setup-keys"
className="block px-4 py-2 text-base font-medium font-mono text-gray-500 hover:text-gray-800 hover:bg-gray-100"
>
Setup Keys
</Link>
)}
{isAuthenticated && (
<Link
to="/acls"
className="block px-4 py-2 text-base font-medium font-mono text-gray-500 hover:text-gray-800 hover:bg-gray-100"
>
Access Control
</Link>
)}
{isAuthenticated && (
<Link
to="/activity"
className="block px-4 py-2 text-base font-medium font-mono text-gray-500 hover:text-gray-800 hover:bg-gray-100"
>
Activity
</Link>
)}
</div>
<div className="pt-4 pb-3 border-t border-gray-200">
<div className="flex items-center px-4">
<div className="flex-shrink-0">
<img
className="h-10 w-10 rounded-full"
src={user.picture}
alt=""
/>
</div>
<div className="ml-3">
<div className="text-base font-medium text-gray-800 font-mono">{user.email}</div>
</div>
</div>
<div className="mt-3 space-y-1">
<Link
to="#"
className="block px-4 py-2 text-base font-medium font-mono text-gray-500 hover:text-gray-800 hover:bg-gray-100"
onClick={() => logoutWithRedirect()}
>
Sign out
</Link>
</div>
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
);
};
export default Navbar;

View File

@@ -0,0 +1,60 @@
import LinuxTab from "./LinuxTab";
import {useState} from "react";
import {classNames} from "../../utils/common";
import WindowsTab from "./WindowsTab";
import MacTab from "./MacTab";
const tabs = [
{name: 'Linux', idx: 1},
{name: 'Windows', idx: 2},
{name: 'MacOS', idx: 3}
]
const AddPeerTabSelector = ({setupKey}) => {
const [openTab, setOpenTab] = useState(1);
return (
<div>
<div className="hidden sm:block">
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8 font-mono" aria-label="Tabs">
{tabs.map((tab) => (
<button
key={tab.name}
onClick={e => {
e.preventDefault();
setOpenTab(tab.idx)
}}
className={classNames(
tab.idx === openTab
? 'border-indigo-500 text-gray-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
'whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm'
)}
aria-current={tab.idx === openTab ? 'page' : undefined}
>
{tab.name}
</button>
))}
</nav>
</div>
</div>
<div className="w-full mx-auto sm:px-6 lg:px-8">
<div className="px-4 py-8 sm:px-0">
<div className={openTab === 1 ? "block" : "hidden"} id="linux-installation-steps">
<LinuxTab setupKey={setupKey}/>
</div>
<div className={openTab === 2 ? "block" : "hidden"} id="windows-installation-steps">
<WindowsTab setupKey={setupKey}/>
</div>
<div className={openTab === 3 ? "block" : "hidden"} id="macos-installation-steps">
<MacTab setupKey={setupKey}/>
</div>
</div>
</div>
</div>
)
}
export default AddPeerTabSelector;

View File

@@ -0,0 +1,118 @@
import ArrowCircleRightIcon from "@heroicons/react/outline/ArrowCircleRightIcon";
import Highlight from "../Highlight";
import CopyButton from "../CopyButton";
import {classNames} from "../../utils/common";
import PropTypes from "prop-types";
import WindowsTab from "./WindowsTab";
const LinuxTab = ({setupKey}) => {
const steps = [
{
id: 1,
target: 'Add Wiretrustee\'s repository:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
commands: ["sudo apt update && sudo apt install gnupg2 curl", "curl -fsSL https://wiretrustee.github.io/key.gpg | sudo apt-key add -", "curl -fsSL https://wiretrustee.github.io/dists/linux/lists | sudo tee /etc/apt/sources.list.d/wiretrustee.list"],
copy: true
},
{
id: 2,
target: 'Install Wiretrustee:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["sudo apt-get update", "sudo apt-get install wiretrustee"]
},
{
id: 3,
target: 'Login and run Wiretrustee:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["sudo wiretrustee login --setup-key <PASTE-SETUP-KEY>", 'sudo systemctl start wiretrustee']
},
{
id: 4,
target: 'Get your IP address:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["ip addr show wt0"]
},
{
id: 5,
target: 'Repeat on other machines.',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
copy: false,
content: null,
commands: null
},
]
const formatCommands = (commands, key) => {
return commands.map(c => key != null ? c.replace("<PASTE-SETUP-KEY>", key.Key) : c).join("\n")
}
return (
<ol role="list" className="overflow-hidden">
{steps.map((step, stepIdx) => (
<li key={"linux-tab-step-" + step.id}
className={classNames(stepIdx !== steps.length - 1 ? 'pb-10' : '', 'relative')}>
<>
{stepIdx !== steps.length - 1 ? (
<div
className="-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full bg-gray-300"
aria-hidden="true"/>
) : null}
<a href={step.href} className="relative flex items-start group">
<span className="h-9 " aria-hidden="true">
<span
className="relative z-10 w-8 h-8 flex items-center justify-center bg-white border-2 border-gray-300 squared-full group-hover:border-gray-400">
<span className="text-m font-mono text-gray-700">{step.id}</span>
</span>
</span>
<span className="ml-4 min-w-0 ">
<span className="text-m tracking-wide font-mono text-gray-700">{step.target}</span>
<div className="flex flex-col space-y-2 ">
<span
className="text-sm text-gray-500">
{
step.content != null ? (step.content) : (
step.commands && (<Highlight language="bash">
{formatCommands(step.commands, setupKey)}
</Highlight>)
)
}
</span>
{step.copy && (<CopyButton toCopy={formatCommands(step.commands, setupKey)}
idPrefix={"add-peer-code-" + step.id}/>)}
</div>
</span>
</a>
</>
</li>
))}
</ol>
)
}
export default LinuxTab;
LinuxTab.propTypes = {
setupKey: PropTypes.object,
};
LinuxTab.defaultProps = {};

View File

@@ -0,0 +1,131 @@
import ArrowCircleRightIcon from "@heroicons/react/outline/ArrowCircleRightIcon";
import Highlight from "../Highlight";
import CopyButton from "../CopyButton";
import {classNames} from "../../utils/common";
import PropTypes from "prop-types";
const MacTab = ({setupKey}) => {
const steps = [
{
id: 1,
target: 'Download latest release (Darwin asset):',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: <button className="underline text-indigo-500" onClick={()=> window.open("https://github.com/wiretrustee/wiretrustee/releases", "_blank")}>Wiretrustee GitHub Releases</button>,
//content: <a href="https://github.com/wiretrustee/wiretrustee/releases">Wiretrustee GitHub Releases</a>,
commands: [],
copy: false
},
{
id: 2,
target: 'Decompress and move to a fixed path in your system:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["tar -xvzf wiretrustee_0.1.0-rc-1_darwin_amd64.tar.gz","sudo mv wiretrusee /usr/local/bin/wiretrustee", "sudo chmod +x /usr/local/bin/wiretrustee"]
},
{
id: 3,
target: 'Configure MAC\'s PATH environment variable:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["export PATH=$PATH:/usr/local/bin"]
},
{
id: 4,
target: 'Login and run Wiretrustee:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["sudo wiretrustee login --setup-key <PASTE-SETUP-KEY>", "sudo wiretrustee up &"]
},
{
id: 5,
target: 'Get your IP address:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["sudo ipconfig getifaddr utun100"]
},
{
id: 6,
target: 'Repeat on other machines.',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
copy: false,
content: null,
commands: null
},
]
const formatCommands = (commands, key) => {
return commands.map(c => key != null ? c.replace("<PASTE-SETUP-KEY>", key.Key) : c).join("\n")
}
return (
<ol role="list" className="overflow-hidden">
{steps.map((step, stepIdx) => (
<li key={"linux-tab-step-" + step.id}
className={classNames(stepIdx !== steps.length - 1 ? 'pb-10' : '', 'relative')}>
<>
{stepIdx !== steps.length - 1 ? (
<div
className="-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full bg-gray-300"
aria-hidden="true"/>
) : null}
<a href={step.href} className="relative flex items-start group">
<span className="h-9 " aria-hidden="true">
<span
className="relative z-10 w-8 h-8 flex items-center justify-center bg-white border-2 border-gray-300 squared-full group-hover:border-gray-400">
<span className="text-m font-mono text-gray-700">{step.id}</span>
</span>
</span>
<span className="ml-4 min-w-0 ">
<span className="text-m tracking-wide font-mono text-gray-700">{step.target}</span>
<div className="flex flex-col space-y-2 ">
<span
className="text-sm text-gray-500">
{
step.content != null ? (
<div className="font-mono underline mt-4">
{step.content}
</div>
) : (
step.commands && (<Highlight language="bash">
{formatCommands(step.commands, setupKey)}
</Highlight>)
)
}
</span>
{step.copy && (<CopyButton toCopy={formatCommands(step.commands, setupKey)}
idPrefix={"add-peer-code-" + step.id}/>)}
</div>
</span>
</a>
</>
</li>
))}
</ol>
)
}
export default MacTab;
MacTab.propTypes = {
setupKey: PropTypes.object,
};
MacTab.defaultProps = {};

View File

@@ -0,0 +1,103 @@
import {Fragment, useState} from 'react'
import {Listbox, Transition} from '@headlessui/react'
import {CheckIcon, SelectorIcon} from '@heroicons/react/solid'
import CopyButton from "../CopyButton";
import {classNames} from "../../utils/common";
import PropTypes from "prop-types";
const SetupKeySelect = ({data, onSelected}) => {
const [selected, setSelected] = useState(data.length > 0 ? data[0] : {Name: "...", Id: "none"})
const handleSelected = selectedKey => {
setSelected(selectedKey)
onSelected(selectedKey)
let keyBox = document.getElementById("key-box");
keyBox.classList.remove("hidden")
};
return (
<div>
<span className="text-m tracking-wide font-mono text-gray-700">Select setup key to register peer:</span>
<span className="ml-4 min-w-0">
<div className="flex flex-col space-y-2">
<Listbox value={selected} onChange={handleSelected}>
{({open}) => (
<>
<div className="mt-1 relative">
<Listbox.Button
className="bg-white relative w-full border border-gray-300 squared-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<span className="block truncate font-mono">{selected.Name}</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true"/>
</span>
</Listbox.Button>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options
className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
{data.map((item) => (
<Listbox.Option
key={item.Id}
className={({active}) =>
classNames(
active ? 'text-white bg-indigo-600' : 'text-gray-900',
'cursor-default select-none relative py-2 pl-3 pr-9'
)
}
value={item}
>
{({selected, active}) => (
<>
<span className={classNames(selected ? 'font-semibold' : 'font-mono', 'block truncate')}>
{item.Name}
</span>
{selected ? (
<span
className={classNames(
active ? 'text-white' : 'text-indigo-600',
'absolute inset-y-0 right-0 flex items-center pr-4'
)}
>
<CheckIcon className="h-5 w-5" aria-hidden="true"/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
<div id="key-box" className="hidden rounded-md bg-gray-100 p-4">
<div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm font-mono text-gray-700">{selected.Key}</p>
<p className="mt-4 text-sm md:mt-0 md:ml-6">
<CopyButton toCopy={selected.Key}/>
</p>
</div>
</div>
</div>
</span>
</div>
)
}
SetupKeySelect.propTypes = {
data: PropTypes.array,
onSelected: PropTypes.func,
};
SetupKeySelect.defaultProps = {};
export default SetupKeySelect

View File

@@ -0,0 +1,141 @@
import ArrowCircleRightIcon from "@heroicons/react/outline/ArrowCircleRightIcon";
import Highlight from "../Highlight";
import CopyButton from "../CopyButton";
import {classNames} from "../../utils/common";
import PropTypes from "prop-types";
import SetupKeySelect from "./SetupKeySelect";
const WindowsTab = ({setupKey}) => {
const steps = [
{
id: 1,
target: 'Download latest release (Windows asset):',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: <button className="underline text-indigo-500" onClick={()=> window.open("https://github.com/wiretrustee/wiretrustee/releases", "_blank")}>Wiretrustee GitHub Releases</button>,
//content: <a href="https://github.com/wiretrustee/wiretrustee/releases">Wiretrustee GitHub Releases</a>,
commands: [],
copy: false
},
{
id: 2,
target: 'Open Powershell as Administrator.',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: <div/>,
copy: false,
commands: []
},
{
id: 3,
target: 'Decompress and move to a fixed path in your system:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["mkdir C:\\Wiretrustee", "tar -xvzf wiretrustee_0.1.0-rc-1_windows_amd64.tar.gz -C C:\\Wiretrustee"]
},
{
id: 4,
target: 'Install Wiretrustee service:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["C:\\Wiretrustee\\wiretrustee.exe service install"]
},
{
id: 5,
target: 'Login and run Wiretrustee:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["C:\\Wiretrustee\\wiretrustee.exe login --setup-key <PASTE-SETUP-KEY>", 'C:\\Wiretrustee\\wiretrustee.exe service start']
},
{
id: 6,
target: 'Get your IP address:',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
content: null,
copy: true,
commands: ["netsh interface ip show config name=\"wt0\""]
},
{
id: 7,
target: 'Repeat on other machines.',
icon: ArrowCircleRightIcon,
iconBackground: 'bg-gray-600',
copy: false,
content: null,
commands: null
},
]
const formatCommands = (commands, key) => {
return commands.map(c => key != null ? c.replace("<PASTE-SETUP-KEY>", key.Key) : c).join("\n")
}
return (
<ol role="list" className="overflow-hidden">
{steps.map((step, stepIdx) => (
<li key={"linux-tab-step-" + step.id}
className={classNames(stepIdx !== steps.length - 1 ? 'pb-10' : '', 'relative')}>
<>
{stepIdx !== steps.length - 1 ? (
<div
className="-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full bg-gray-300"
aria-hidden="true"/>
) : null}
<a href={step.href} className="relative flex items-start group">
<span className="h-9 " aria-hidden="true">
<span
className="relative z-10 w-8 h-8 flex items-center justify-center bg-white border-2 border-gray-300 squared-full group-hover:border-gray-400">
<span className="text-m font-mono text-gray-700">{step.id}</span>
</span>
</span>
<span className="ml-4 min-w-0 ">
<span className="text-m tracking-wide font-mono text-gray-700">{step.target}</span>
<div className="flex flex-col space-y-2 ">
<span
className="text-sm text-gray-500">
{
step.content != null ? (
<div className="font-mono underline mt-4">
{step.content}
</div>
) : (
step.commands && (<Highlight language="bash">
{formatCommands(step.commands, setupKey)}
</Highlight>)
)
}
</span>
{step.copy && (<CopyButton toCopy={formatCommands(step.commands, setupKey)}
idPrefix={"add-peer-code-" + step.id}/>)}
</div>
</span>
</a>
</>
</li>
))}
</ol>
)
}
export default WindowsTab;
WindowsTab.propTypes = {
setupKey: PropTypes.object,
};
WindowsTab.defaultProps = {};

21
src/config.js Normal file
View File

@@ -0,0 +1,21 @@
import configJson from "./config.json";
export function getConfig() {
// Configure the audience here. By default, it will take whatever is in the config
// (specified by the `audience` key) unless it's the default value of "YOUR_API_IDENTIFIER" (which
// is what you get sometimes by using the Auth0 sample download tool from the quickstart page, if you
// don't have an API).
// If this resolves to `null`, the API page changes to show some helpful info about what to do
// with the audience.
const audience =
configJson.audience && configJson.audience !== "YOUR_API_IDENTIFIER"
? configJson.audience
: null;
return {
domain: configJson.domain,
clientId: configJson.clientId,
apiOrigin: configJson.apiOrigin,
...(audience ? { audience } : null),
};
}

6
src/config.json Normal file
View File

@@ -0,0 +1,6 @@
{
"domain": "$AUTH0_DOMAIN",
"clientId": "$AUTH0_CLIENT_ID",
"audience": "$AUTH0_AUDIENCE",
"apiOrigin": "$WIRETRUSTEE_MGMT_API_ENDPOINT"
}

11
src/index.css Normal file
View File

@@ -0,0 +1,11 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.menu-card {
@apply flex flex-col justify-center items-center bg-white h-screen font-mono py-40;
}
.center-content {
@apply flex flex-col justify-center items-center;
}

43
src/index.js Normal file
View File

@@ -0,0 +1,43 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from 'react-router-dom';
import history from "./utils/history";
import { getConfig } from "./config";
import {Auth0Provider} from "@auth0/auth0-react";
const onRedirectCallback = (appState) => {
history.push(
appState && appState.returnTo ? appState.returnTo : window.location.pathname
);
};
const config = getConfig();
const providerConfig = {
domain: config.domain,
clientId: config.clientId,
...(config.audience ? { audience: config.audience } : null),
redirectUri: window.location.origin,
useRefreshTokens: true,
onRedirectCallback,
};
ReactDOM.render(
<Auth0Provider {...providerConfig}>
<React.StrictMode>
<BrowserRouter>
<App/>
</BrowserRouter>
</React.StrictMode>
</Auth0Provider>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

13
src/reportWebVitals.js Normal file
View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

5
src/setupTests.js Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

74
src/utils/common.js Normal file
View File

@@ -0,0 +1,74 @@
export const formatDate = date => {
return new Date(date).toLocaleDateString("en-GB", { weekday: 'short', year: '2-digit', month: 'short', day: 'numeric' });
}
export const classNames = (...classes) => {
return classes.filter(Boolean).join(' ')
}
const MONTH_NAMES = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
function getFormattedDate(date, preformattedDate = false, hideYear = false) {
const day = date.getDate();
const month = MONTH_NAMES[date.getMonth()];
const year = date.getFullYear();
let minutes = date.getMinutes();
if (minutes < 10) {
// Adding leading zero to minutes
minutes = `0${ minutes }`;
}
if (preformattedDate) {
// Today
// Yesterday
return `${ preformattedDate }`;
}
if (hideYear) {
// 10. January
return `${ day }. ${ month }`;
}
// 10. January 2017.
return `${ day }. ${ month } ${ year }`;
}
export const timeAgo = (dateParam) => {
if (!dateParam) {
return null;
}
const date = typeof dateParam === 'object' ? dateParam : new Date(dateParam);
const DAY_IN_MS = 86400000; // 24 * 60 * 60 * 1000
const today = new Date();
const yesterday = new Date(today - DAY_IN_MS);
const seconds = Math.round((today - date) / 1000);
const minutes = Math.round(seconds / 60);
const isToday = today.toDateString() === date.toDateString();
const isYesterday = yesterday.toDateString() === date.toDateString();
const isThisYear = today.getFullYear() === date.getFullYear();
if (seconds < 5) {
return 'just now';
} else if (seconds < 60) {
return `${ seconds } seconds ago`;
} else if (seconds < 90) {
return 'about a minute ago';
} else if (minutes < 60) {
return `${ minutes } minutes ago`;
} else if (isToday) {
return getFormattedDate(date, 'Today'); // Today at 10:20
} else if (isYesterday) {
return getFormattedDate(date, 'Yesterday'); // Yesterday at 10:20
} else if (isThisYear) {
return getFormattedDate(date, false, true); // 10. January at 10:20
}
return getFormattedDate(date); // 10. January 2017. at 10:20
}

2
src/utils/history.js Normal file
View File

@@ -0,0 +1,2 @@
import { createBrowserHistory } from "history";
export default createBrowserHistory();

View File

@@ -0,0 +1,59 @@
import React, {useState} from "react";
import {withAuthenticationRequired} from "@auth0/auth0-react";
import Loading from "../components/Loading";
export const AccessControlComponent = () => {
const [error] = useState(null)
return (
<>
<div className="py-10">
<header>
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-2xl font-mono leading-tight text-gray-900 font-bold">Access Control</h1>
</div>
</header>
<main>
<div className="max-w-5xl mx-auto sm:px-6 lg:px-8">
<div className="px-4 py-8 sm:px-0">
{error != null && (
<span>{error.toString()}</span>
)}
<h1 className="text-m font-mono leading-tight text-gray-900 font-bold">
Create and control access groups
</h1>
<br/>
<p className="text-sm font-mono">
Here you will be able to specify what peers or groups of peers are able to connect to
each other.
For example, you might have 3 departments in your organization - IT, HR, Finance.
In most cases Finance and HR departments wouldn't need to access machines of the IT
department.
In such scenario you could create 3 separate tags (groups) and label peers accordingly
so that only
peers
from the same group can access each other.
You could also specify what groups can connect to each other and do fine grained control
even on a
peer level.
</p>
<br/>
<p className="text-sm font-mono">Stay tuned.</p>
</div>
</div>
</main>
</div>
</>
);
}
;
export default withAuthenticationRequired(AccessControlComponent,
{
onRedirecting: () => <Loading/>,
}
);

47
src/views/Activity.js Normal file
View File

@@ -0,0 +1,47 @@
import React, {useState} from "react";
import {withAuthenticationRequired} from "@auth0/auth0-react";
import Loading from "../components/Loading";
export const ActivityComponent = () => {
const [error] = useState(null)
return (
<>
<div className="py-10">
<header>
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-2xl font-mono leading-tight text-gray-900 font-bold">Access Control</h1>
</div>
</header>
<main>
<div className="max-w-5xl mx-auto sm:px-6 lg:px-8">
<div className="px-4 py-8 sm:px-0">
{error != null && (
<span>{error.toString()}</span>
)}
<h1 className="text-m font-mono leading-tight text-gray-900 font-bold">
Monitor system activity.
</h1>
<br/>
<p className="text-sm font-mono">
Here you will be able to see activity of peers. E.g. events like Peer A has connected to Peer B
</p>
<br/>
<p className="text-sm font-mono">Stay tuned.</p>
</div>
</div>
</main>
</div>
</>
);
}
;
export default withAuthenticationRequired(ActivityComponent,
{
onRedirecting: () => <Loading/>,
}
);

69
src/views/AddPeer.js Normal file
View File

@@ -0,0 +1,69 @@
import React, {useEffect, useState} from "react";
import {useAuth0, withAuthenticationRequired} from "@auth0/auth0-react";
import Loading from "../components/Loading";
import {getSetupKeys} from "../api/ManagementAPI";
import AddPeerTabSelector from "../components/addpeer/AddPeerTabSelector";
import SetupKeySelect from "../components/addpeer/SetupKeySelect";
export const AddPeerComponent = () => {
const [setupKeys, setSetupKeys] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [selectedKey, setSelectedKey] = useState(null)
const {
getAccessTokenSilently,
} = useAuth0();
const handleError = error => {
console.error('Error to fetch data:', error);
setLoading(false)
setError(error);
};
useEffect(() => {
getSetupKeys(getAccessTokenSilently)
.then(responseData => setSetupKeys(responseData))
.then(() => setLoading(false))
.catch(error => handleError(error))
}, [getAccessTokenSilently])
return (
<>
<div className="py-10">
<header>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-2xl leading-tight text-gray-900 font-mono font-bold">Add Peer</h1>
</div>
</header>
<main>
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="px-4 py-8 sm:px-0">
{loading && (<Loading/>)}
{error != null && (
<span>{error.toString()}</span>
)}
{setupKeys && (<nav aria-label="Progress">
<div className="flex max-w-lg flex-col space-y-2">
<SetupKeySelect data={setupKeys.filter(k => k.Valid)} onSelected={setSelectedKey}/>
</div>
<AddPeerTabSelector setupKey={selectedKey}/>
</nav>)}
</div>
</div>
</main>
</div>
</>
);
}
;
export default withAuthenticationRequired(AddPeerComponent,
{
onRedirecting: () => <Loading/>,
}
);

200
src/views/Peers.js Normal file
View File

@@ -0,0 +1,200 @@
import React, {useEffect, useState} from "react";
import {useAuth0, withAuthenticationRequired} from "@auth0/auth0-react";
import Loading from "../components/Loading";
import {deletePeer, getPeers} from "../api/ManagementAPI";
import {timeAgo} from "../utils/common";
import EditButton from "../components/EditButton";
import CopyText from "../components/CopyText";
import DeleteModal from "../components/DeleteDialog";
import EmptyPeersPanel from "../components/EmptyPeers";
export const Peers = () => {
const [peers, setPeers] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [deleteDialogText, setDeleteDialogText] = useState("")
const [deleteDialogTitle, setDeleteDialogTitle] = useState("")
const [peerToDelete, setPeerToDelete] = useState(null)
const {
getAccessTokenSilently,
} = useAuth0();
const handleError = error => {
console.error('Error to fetch data:', error);
setLoading(false)
setError(error);
};
//called when user clicks on table row menu item
const handleRowMenuClick = (action, peer) => {
if (action === 'Delete') {
setPeerToDelete(peer)
setDeleteDialogText("Are you sure you want to delete peer from your account?")
setDeleteDialogTitle("Delete peer \"" + peer.Name + "\"")
setShowDeleteDialog(true)
}
};
const refresh = () => {
getPeers(getAccessTokenSilently)
.then(responseData => responseData.sort((a, b) => (a.Name > b.Name) ? 1 : -1))
.then(sorted => setPeers(sorted))
.then(() => setLoading(false))
.catch(error => handleError(error))
}
// after user confirms (or not) deletion of the peer
const handleDeleteConfirmation = (confirmed) => {
setShowDeleteDialog(false)
if (confirmed) {
deletePeer(getAccessTokenSilently, peerToDelete.IP)
.then(() => setPeerToDelete(null))
.then(() => refresh())
.catch(error => {
setPeerToDelete(null)
console.log(error)
})
} else {
setPeerToDelete(null)
}
}
useEffect(() => {
refresh()
}, [getAccessTokenSilently])
return (
<>
<div className="py-10">
<header>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-2xl font-mono leading-tight text-gray-900 font-bold">Peers</h1>
</div>
</header>
<main>
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="px-4 py-8 sm:px-0">
{loading && (<Loading/>)}
{error != null && (
<span>{error.toString()}</span>
)}
<main>
{loading && (<Loading/>)}
{error != null && (
<span>{error.toString()}</span>
)}
{peers.length === 0 ?
(<EmptyPeersPanel/>) : (
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="px-4 py-8 sm:px-0">
<DeleteModal show={showDeleteDialog}
confirmCallback={handleDeleteConfirmation}
text={deleteDialogText} title={deleteDialogTitle}/>
<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 className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-100">
<tr>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Name
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
IP
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Status
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Last Seen
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
OS
</th>
<th scope="col" className="relative px-6 py-3">
<span className="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{peers.map((peer, idx) => (
<tr key={peer.IP}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium font-semibold font-mono text-gray-900">{peer.Name}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium font-mono text-gray-900">
<CopyText text={peer.IP.toUpperCase()}
idPrefix={"peers-ip-" + peer.IP}/>
</td>
<td className="px-6 py-4 whitespace-nowrap">
{peer.Connected && (
<span
className="px-2 inline-flex text-sm leading-5 font-mono squared-full bg-green-100 text-green-800">
Connected
</span>
)}
{!peer.Connected && (
<span
className="px-2 inline-flex text-sm leading-5 font-mono squared-full bg-red-100 text-red-800">
Disconnected
</span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-700">
{peer.Connected ? ("just now") : timeAgo(peer.LastSeen)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-700">{peer.OS}</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-m font-medium">
<EditButton items={[{name: "Delete"}]}
handler={action => handleRowMenuClick(action, peer)}/>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</main>
</div>
</div>
</main>
</div>
</>
);
}
;
export default withAuthenticationRequired(Peers,
{
onRedirecting: () => <Loading/>,
}
);

199
src/views/SetupKeys.js Normal file
View File

@@ -0,0 +1,199 @@
import React, {useEffect, useState} from "react";
import {useAuth0, withAuthenticationRequired} from "@auth0/auth0-react";
import Loading from "../components/Loading";
import {formatDate, timeAgo} from "../utils/common";
import {getSetupKeys, revokeSetupKey} from "../api/ManagementAPI";
import EditButton from "../components/EditButton";
import CopyText from "../components/CopyText";
import DeleteModal from "../components/DeleteDialog";
export const SetupKeysComponent = () => {
const [setupKeys, setSetupKeys] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [deleteDialogText, setDeleteDialogText] = useState("")
const [deleteDialogTitle, setDeleteDialogTitle] = useState("")
const [keyToRevoke, setKeyToRevoke] = useState(null)
const {
getAccessTokenSilently,
} = useAuth0();
const handleError = error => {
console.error('Error to fetch data:', error);
setLoading(false)
setError(error);
};
//called when user clicks on table row menu item
const handleRowMenuClick = (action, key) => {
if (action === 'Revoke') {
setKeyToRevoke(key)
setDeleteDialogText("Are you sure you want to revoke setup key?")
setDeleteDialogTitle("Revoke key \"" + key.Name + "\"")
setShowDeleteDialog(true)
}
};
// after user confirms (or not) revoking the key
const handleRevokeConfirmation = (confirmed) => {
setShowDeleteDialog(false)
if (confirmed && !keyToRevoke.Revoked) {
revokeSetupKey(getAccessTokenSilently, keyToRevoke.Id)
.then(() => setKeyToRevoke(null))
.then(() => refresh())
.catch(error => {
setKeyToRevoke(null)
console.log(error)
})
} else {
setKeyToRevoke(null)
}
}
const refresh = () => {
getSetupKeys(getAccessTokenSilently)
.then(responseData => responseData.sort((a, b) => (a.Name > b.Name) ? 1 : -1))
.then(sorted => setSetupKeys(sorted))
.then(() => setLoading(false))
.catch(error => handleError(error))
}
useEffect(() => {
refresh()
}, [getAccessTokenSilently])
return (
<>
<div className="py-10">
<header>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-2xl font-mono leading-tight text-gray-900 font-bold">Setup Keys</h1>
</div>
</header>
<main>
{loading && (<Loading/>)}
{error != null && (
<span>{error.toString()}</span>
)}
{setupKeys && (
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="px-4 py-8 sm:px-0">
<DeleteModal show={showDeleteDialog}
confirmCallback={handleRevokeConfirmation}
text={deleteDialogText} title={deleteDialogTitle}/>
<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 className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-100">
<tr>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Name
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
State
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Type
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Key
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Last Used
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Used Times
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Expires
</th>
<th scope="col" className="relative px-6 py-3">
<span className="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{setupKeys.map((setupKey, idx) => (
<tr key={setupKey.Id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium font-semibold font-mono text-gray-900">{setupKey.Name}</td>
<td className="px-6 py-4 whitespace-nowrap">
{setupKey.Valid && (
<span
className="px-2 inline-flex text-sm leading-5 font-mono squared-full bg-green-100 text-green-800">
valid
</span>
)}
{!setupKey.Valid && (
<span
className="px-2 inline-flex text-sm leading-5 font-mono squared-full bg-red-100 text-red-800">
{setupKey.State}
</span>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-700">{setupKey.Type.toLowerCase()}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-700">
<CopyText text={setupKey.Key.toUpperCase()}
idPrefix={"setup-keys" + setupKey.Id}/>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-700">{setupKey.UsedTimes === 0 ? "unused" : timeAgo(setupKey.LastUsed)}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-700">{setupKey.UsedTimes}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-700">{formatDate(setupKey.Expires)}</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-m font-medium">
<EditButton items={[{name: "Revoke"}]}
handler={action => handleRowMenuClick(action, setupKey)}/>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</main>
</div>
</>
);
}
;
export default withAuthenticationRequired(SetupKeysComponent,
{
onRedirecting: () => <Loading/>,
}
);