mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
initial commit
This commit is contained in:
48
.github/workflows/build_and_push.yml
vendored
Normal file
48
.github/workflows/build_and_push.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: build and push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
build_n_push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: setup-node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependecies
|
||||
run: npm install
|
||||
|
||||
- name: Build
|
||||
# skiping fail on warning for now
|
||||
run: CI=false npm run build
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: wiretrustee/dashboard
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
-
|
||||
name: Docker build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
src/auth_config.json
|
||||
.idea
|
||||
.eslintcache
|
||||
70
README.md
Normal file
70
README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
Use the [Docker documentation](docker/README.md).
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
7
craco.config.js
Normal file
7
craco.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
style: {
|
||||
postcss: {
|
||||
plugins: [require('tailwindcss'), require('autoprefixer')]
|
||||
}
|
||||
}
|
||||
};
|
||||
24
docker/Dockerfile
Normal file
24
docker/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM alpine:3.14
|
||||
|
||||
RUN apk add --no-cache bash curl less ca-certificates git tzdata zip gettext \
|
||||
nginx curl supervisor certbot-nginx && \
|
||||
rm -rf /var/cache/apk/* && mkdir -p /run/nginx
|
||||
|
||||
STOPSIGNAL SIGINT
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
ENTRYPOINT ["/usr/bin/supervisord","-c","/etc/supervisord.conf"]
|
||||
|
||||
WORKDIR /usr/share/nginx/html
|
||||
# copy configuration files
|
||||
COPY docker/default.conf /etc/nginx/http.d/default.conf
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/init_cert.sh /usr/local/init_cert.sh
|
||||
COPY docker/init_react_envs.sh /usr/local/init_react_envs.sh
|
||||
RUN chmod +x /usr/local/init_cert.sh && rm /etc/crontabs/root
|
||||
RUN chmod +x /usr/local/init_react_envs.sh
|
||||
|
||||
# configure supervisor
|
||||
COPY docker/supervisord.conf /etc/supervisord.conf
|
||||
# copy build files
|
||||
COPY build/ /usr/share/nginx/html/
|
||||
23
docker/README.md
Normal file
23
docker/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Wiretrustee Dashboard
|
||||
Wiretrustee Dashboard is a the Wiretrustee Managemenet server UI. It allow users to signin, view setup keys and manage peers. This image is **not ready** for production use.
|
||||
## Tags
|
||||
```latest``` ```vX.X.X``` not available yet.
|
||||
|
||||
```main``` builded on every PR being merged to the repository
|
||||
## How to use this image
|
||||
HTTP run:
|
||||
```shell
|
||||
docker run -d --rm -p 80:80 wiretrustee/dashboard:main
|
||||
```
|
||||
Using SSL certificate from Let's Encript®:
|
||||
```shell
|
||||
docker run -d --rm -p 80:80 -p 443:443 \
|
||||
-e LETSENCRYPT_DOMAIN=app.mydomain.com \
|
||||
-e LETSENCRYPT_EMAIL=hello@mydomain.com \
|
||||
wiretrustee/dashboard:main
|
||||
```
|
||||
> For SSL generation, you need to run this image in a server with proper public IP and a domain name pointing to it.
|
||||
## Environment variables
|
||||
* ```NGINX_SSL_PORT``` Changes the port that Nginx listens to. Defaults to ```443```
|
||||
* ```LETSENCRYPT_DOMAIN``` Enables Certbot`s client execution for the specified domain. Defaults to ```none```
|
||||
* ```LETSENCRYPT_EMAIL``` Email used in Certbot`s client execution to register the certificate request. Defaults to ```example@local```
|
||||
16
docker/default.conf
Normal file
16
docker/default.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
# simple server configuration to replace nginx's default
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
location / {
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
# You may need this to prevent return 404 recursion.
|
||||
location = /404.html {
|
||||
internal;
|
||||
}
|
||||
|
||||
}
|
||||
22
docker/init_cert.sh
Normal file
22
docker/init_cert.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
LETSENCRYPT_DOMAIN=${LETSENCRYPT_DOMAIN:-"none"}
|
||||
LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL:-"example@local"}
|
||||
NGINX_SSL_PORT=${NGINX_SSL_PORT:-443}
|
||||
|
||||
# If no domain is provided, skip certbot execution and configuration
|
||||
if [ "${LETSENCRYPT_DOMAIN}-x" == "none-x" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Request a certificate
|
||||
# this also updates the nginx config file with new SSL entries
|
||||
certbot -n --nginx --agree-tos --email ${LETSENCRYPT_EMAIL} -d ${LETSENCRYPT_DOMAIN} --https-port ${NGINX_SSL_PORT}
|
||||
# Add cron job file
|
||||
cat <<EOF >/etc/crontabs/certbot
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||
|
||||
0 */12 * * * root certbot -q renew --nginx --https-port ${NGINX_SSL_PORT}
|
||||
EOF
|
||||
# start cron daemon
|
||||
supervisorctl start cron
|
||||
37
docker/init_react_envs.sh
Normal file
37
docker/init_react_envs.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [[ -z "${AUTH0_DOMAIN}" ]]; then
|
||||
echo "AUTH0_DOMAIN environment variable must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${AUTH0_CLIENT_ID}" ]]; then
|
||||
echo "AUTH0_CLIENT_ID environment variable must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${AUTH0_AUDIENCE}" ]]; then
|
||||
echo "AUTH0_AUDIENCE environment variable must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${WIRETRUSTEE_MGMT_API_ENDPOINT}" ]]; then
|
||||
echo "WIRETRUSTEE_MGMT_API_ENDPOINT environment variable must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AUTH0_DOMAIN=${AUTH0_DOMAIN}
|
||||
AUTH0_CLIENT_ID=${AUTH0_CLIENT_ID}
|
||||
AUTH0_AUDIENCE=${AUTH0_AUDIENCE}
|
||||
WIRETRUSTEE_MGMT_API_ENDPOINT=${WIRETRUSTEE_MGMT_API_ENDPOINT}
|
||||
|
||||
# replace ENVs in the config
|
||||
ENV_STR="\$\$AUTH0_DOMAIN \$\$AUTH0_CLIENT_ID \$\$AUTH0_AUDIENCE \$\$WIRETRUSTEE_MGMT_API_ENDPOINT"
|
||||
MAIN_JS=$(find /usr/share/nginx/html/static/js/main.*js)
|
||||
cp "$MAIN_JS" "$MAIN_JS".copy
|
||||
envsubst "$ENV_STR" < "$MAIN_JS".copy > "$MAIN_JS"
|
||||
rm "$MAIN_JS".copy
|
||||
|
||||
|
||||
|
||||
142
docker/nginx.conf
Normal file
142
docker/nginx.conf
Normal file
@@ -0,0 +1,142 @@
|
||||
# /etc/nginx/nginx.conf
|
||||
daemon off;
|
||||
|
||||
user nginx;
|
||||
|
||||
# Set number of worker processes automatically based on number of CPU cores.
|
||||
worker_processes auto;
|
||||
|
||||
# Enables the use of JIT for regular expressions to speed-up their processing.
|
||||
pcre_jit on;
|
||||
|
||||
# Configures default error logger.
|
||||
error_log /proc/self/fd/2 warn;
|
||||
|
||||
# Includes files with directives to load dynamic modules.
|
||||
include /etc/nginx/modules/*.conf;
|
||||
|
||||
# Uncomment to include files with config snippets into the root context.
|
||||
# NOTE: This will be enabled by default in Alpine 3.15.
|
||||
#include /etc/nginx/conf.d/*.conf;
|
||||
|
||||
events {
|
||||
# The maximum number of simultaneous connections that can be opened by
|
||||
# a worker process.
|
||||
worker_connections 1024;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
# Includes mapping of file name extensions to MIME types of responses
|
||||
# and defines the default type.
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Name servers used to resolve names of upstream servers into addresses.
|
||||
# It's also needed when using tcpsocket and udpsocket in Lua modules.
|
||||
#resolver 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001;
|
||||
|
||||
# Don't tell nginx version to the clients. Default is 'on'.
|
||||
server_tokens off;
|
||||
|
||||
# Specifies the maximum accepted body size of a client request, as
|
||||
# indicated by the request header Content-Length. If the stated content
|
||||
# length is greater than this size, then the client receives the HTTP
|
||||
# error code 413. Set to 0 to disable. Default is '1m'.
|
||||
client_max_body_size 1m;
|
||||
|
||||
# Sendfile copies data between one FD and other from within the kernel,
|
||||
# which is more efficient than read() + write(). Default is off.
|
||||
sendfile on;
|
||||
|
||||
# Causes nginx to attempt to send its HTTP response head in one packet,
|
||||
# instead of using partial frames. Default is 'off'.
|
||||
tcp_nopush on;
|
||||
|
||||
|
||||
# Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2.
|
||||
# TIP: If you're not obligated to support ancient clients, remove TLSv1.1.
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
# Path of the file with Diffie-Hellman parameters for EDH ciphers.
|
||||
# TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048`
|
||||
#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
|
||||
|
||||
# Specifies that our cipher suits should be preferred over client ciphers.
|
||||
# Default is 'off'.
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Enables a shared SSL cache with size that can hold around 8000 sessions.
|
||||
# Default is 'none'.
|
||||
ssl_session_cache shared:SSL:2m;
|
||||
|
||||
# Specifies a time during which a client may reuse the session parameters.
|
||||
# Default is '5m'.
|
||||
ssl_session_timeout 1h;
|
||||
|
||||
# Disable TLS session tickets (they are insecure). Default is 'on'.
|
||||
ssl_session_tickets off;
|
||||
|
||||
|
||||
# Enable gzipping of responses.
|
||||
gzip on;
|
||||
|
||||
# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
|
||||
gzip_vary on;
|
||||
|
||||
# General gzip configurations
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 9;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_disable "msie6";
|
||||
# Compress the following MIME types.
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/javascript
|
||||
application/x-javascript
|
||||
application/json
|
||||
application/ld+json
|
||||
application/manifest+json
|
||||
application/rss+xml
|
||||
application/vnd.geo+json
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
application/x-web-app-manifest+json
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/opentype
|
||||
image/bmp
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
text/cache-manifest
|
||||
text/css
|
||||
text/javascript
|
||||
text/plain
|
||||
text/vcard
|
||||
text/vnd.rim.location.xloc
|
||||
text/vtt
|
||||
text/x-component
|
||||
text/x-cross-domain-policy;
|
||||
# Helper variable for proxying websockets.
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
|
||||
# Specifies the main log format.
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
# Sets the path, format, and configuration for a buffered log write.
|
||||
access_log /proc/self/fd/1 main;
|
||||
|
||||
|
||||
# Includes virtual hosts configs.
|
||||
include /etc/nginx/http.d/*.conf;
|
||||
}
|
||||
|
||||
# TIP: Uncomment if you use stream module.
|
||||
#include /etc/nginx/stream.conf;
|
||||
67
docker/supervisord.conf
Normal file
67
docker/supervisord.conf
Normal file
@@ -0,0 +1,67 @@
|
||||
[unix_http_server]
|
||||
file=/var/run/supervisor.sock
|
||||
chmod=0700
|
||||
username = dummy
|
||||
password = dummy
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///var/run/supervisor.sock
|
||||
username = dummy
|
||||
password = dummy
|
||||
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
|
||||
pidfile=/run/supervisord.pid
|
||||
|
||||
logfile=/proc/self/fd/1
|
||||
logfile_maxbytes=0
|
||||
loglevel=warn
|
||||
|
||||
[program:cron]
|
||||
command=crond -f
|
||||
user=root
|
||||
priority=101
|
||||
numprocs=1
|
||||
autostart=0
|
||||
autorestart=true
|
||||
|
||||
stdout_logfile=/proc/self/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/proc/self/fd/2
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:nginx]
|
||||
command=/usr/sbin/nginx
|
||||
user=root
|
||||
priority=100
|
||||
numprocs=1
|
||||
autostart=1
|
||||
autorestart=true
|
||||
|
||||
stdout_logfile=/proc/self/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/proc/self/fd/2
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:init_cert]
|
||||
command=/usr/local/init_cert.sh
|
||||
user=root
|
||||
|
||||
[program:init_react_envs]
|
||||
command=/usr/local/init_react_envs.sh
|
||||
user=root
|
||||
|
||||
numprocs=1
|
||||
autostart=1
|
||||
autorestart=false
|
||||
startretries=1
|
||||
priority=200
|
||||
stdout_logfile=/proc/self/fd/1
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/proc/self/fd/2
|
||||
stderr_logfile_maxbytes=0
|
||||
37112
package-lock.json
generated
Normal file
37112
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
52
package.json
Normal file
52
package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "wiretrustee-dashboard",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@auth0/auth0-react": "^1.6.0",
|
||||
"@craco/craco": "^6.0.0",
|
||||
"@headlessui/react": "^1.4.0",
|
||||
"@tailwindcss/postcss7-compat": "^2.0.2",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"autoprefixer": "^9",
|
||||
"highlight.js": "^11.2.0",
|
||||
"history": "^5.0.1",
|
||||
"postcss": "^7",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.1",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
|
||||
"web-vitals": "^0.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@heroicons/react": "^1.0.4"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
42
public/index.html
Normal file
42
public/index.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Wiretrustee</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
15
public/manifest.json
Normal file
15
public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
3
public/robots.txt
Normal file
3
public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
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/>,
|
||||
}
|
||||
);
|
||||
11
tailwind.config.js
Normal file
11
tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
variants: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
};
|
||||
Reference in New Issue
Block a user