mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
initial commit
This commit is contained in:
86
src/App.js
Normal file
86
src/App.js
Normal 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
8
src/App.test.js
Normal 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
55
src/api/ManagementAPI.js
Normal 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
52
src/assets/bars.svg
Normal 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
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
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
28
src/components/Content.js
Normal 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;
|
||||
39
src/components/CopyButton.js
Normal file
39
src/components/CopyButton.js
Normal 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;
|
||||
36
src/components/CopyText.js
Normal file
36
src/components/CopyText.js
Normal 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;
|
||||
103
src/components/DeleteDialog.js
Normal file
103
src/components/DeleteDialog.js
Normal 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">
|
||||
​
|
||||
</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;
|
||||
|
||||
56
src/components/EditButton.js
Normal file
56
src/components/EditButton.js
Normal 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;
|
||||
28
src/components/EmptyPeers.js
Normal file
28
src/components/EmptyPeers.js
Normal 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
13
src/components/Footer.js
Normal 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
34
src/components/Hero.js
Normal 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;
|
||||
73
src/components/Highlight.js
Normal file
73
src/components/Highlight.js
Normal 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
13
src/components/Loading.js
Normal 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
228
src/components/Navbar.js
Normal 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;
|
||||
60
src/components/addpeer/AddPeerTabSelector.js
Normal file
60
src/components/addpeer/AddPeerTabSelector.js
Normal 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;
|
||||
118
src/components/addpeer/LinuxTab.js
Normal file
118
src/components/addpeer/LinuxTab.js
Normal 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 = {};
|
||||
131
src/components/addpeer/MacTab.js
Normal file
131
src/components/addpeer/MacTab.js
Normal 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 = {};
|
||||
103
src/components/addpeer/SetupKeySelect.js
Normal file
103
src/components/addpeer/SetupKeySelect.js
Normal 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
|
||||
141
src/components/addpeer/WindowsTab.js
Normal file
141
src/components/addpeer/WindowsTab.js
Normal 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
21
src/config.js
Normal 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
6
src/config.json
Normal 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
11
src/index.css
Normal 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
43
src/index.js
Normal 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
13
src/reportWebVitals.js
Normal 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
5
src/setupTests.js
Normal 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
74
src/utils/common.js
Normal 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
2
src/utils/history.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import { createBrowserHistory } from "history";
|
||||
export default createBrowserHistory();
|
||||
59
src/views/AccessControl.js
Normal file
59
src/views/AccessControl.js
Normal 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
47
src/views/Activity.js
Normal 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
69
src/views/AddPeer.js
Normal 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
200
src/views/Peers.js
Normal 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
199
src/views/SetupKeys.js
Normal 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/>,
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user