mirror of
https://github.com/dec0dOS/zero-ui.git
synced 2025-08-20 21:33:55 -07:00
Compare commits
No commits in common. "main" and "v1.0.0" have entirely different histories.
109 changed files with 15803 additions and 12163 deletions
|
@ -1,9 +1,8 @@
|
||||||
.git
|
.git
|
||||||
.github
|
|
||||||
*Dockerfile*
|
*Dockerfile*
|
||||||
*docker-compose*
|
*docker-compose*
|
||||||
node_modules
|
node_modules
|
||||||
.eslintrc.json
|
jsconfig.js
|
||||||
.DS_Store
|
.DS_Store
|
||||||
tmp
|
tmp
|
||||||
temp
|
temp
|
||||||
|
@ -13,6 +12,4 @@ npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
.yarn/cache
|
backend/data
|
||||||
db.json
|
|
||||||
backend/data/db.json
|
|
||||||
|
|
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -1 +0,0 @@
|
||||||
* @dec0dOS
|
|
23
.github/ISSUE_TEMPLATE/01_BUG_REPORT.md
vendored
23
.github/ISSUE_TEMPLATE/01_BUG_REPORT.md
vendored
|
@ -1,39 +1,34 @@
|
||||||
---
|
---
|
||||||
name: Bug Report
|
name: Bug Report
|
||||||
about: Create a report to help ZeroUI to improve
|
about: Create a report to help ZeroUI to improve
|
||||||
title: "bug: "
|
title: 'bug: '
|
||||||
labels: "bug"
|
labels: ''
|
||||||
assignees: ""
|
assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION. -->
|
||||||
|
|
||||||
# Bug Report
|
# Bug Report
|
||||||
|
|
||||||
**ZeroUI version:**
|
**ZeroUI version:**
|
||||||
|
|
||||||
<!-- Please specify commit or tag version. -->
|
<!-- Please specify commit or tag version. -->
|
||||||
|
|
||||||
latest
|
latest
|
||||||
|
|
||||||
**Current behavior:**
|
**Current behavior:**
|
||||||
|
|
||||||
<!-- Describe how the bug manifests. -->
|
<!-- Describe how the bug manifests. -->
|
||||||
|
|
||||||
**Expected behavior:**
|
**Expected behavior:**
|
||||||
|
<!-- Describe what the behavior would be without the bug. -->
|
||||||
<!-- Describe what you expect the behavior to be without the bug. -->
|
|
||||||
|
|
||||||
**Steps to reproduce:**
|
**Steps to reproduce:**
|
||||||
|
<!-- Please explain the steps required to duplicate the issue, especially if you are able to provide a sample application. -->
|
||||||
<!-- Explain the steps required to duplicate the issue, especially if you are able to provide a sample application. -->
|
|
||||||
|
|
||||||
**Related code:**
|
**Related code:**
|
||||||
|
<!-- If you are able to illustrate the bug or feature request with an example, please provide it here -->
|
||||||
<!-- If you are able to illustrate the bug or feature request with an example, please provide it here. -->
|
|
||||||
|
|
||||||
```
|
```
|
||||||
insert short code snippets here
|
insert short code snippets here
|
||||||
```
|
```
|
||||||
|
|
||||||
**Other information:**
|
**Other information:**
|
||||||
|
<!-- List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. -->
|
||||||
<!-- List any other information that is relevant to your issue. Related issues, suggestions on how to fix, Stack Overflow links, forum links, etc. -->
|
|
||||||
|
|
15
.github/ISSUE_TEMPLATE/02_FEATURE_REQUEST.md
vendored
15
.github/ISSUE_TEMPLATE/02_FEATURE_REQUEST.md
vendored
|
@ -1,31 +1,28 @@
|
||||||
---
|
---
|
||||||
name: Feature Request
|
name: Feature Request
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: "feat: "
|
title: 'feat: '
|
||||||
labels: "new-feature"
|
labels: ''
|
||||||
assignees: ""
|
assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION. -->
|
||||||
|
|
||||||
# Feature Request
|
# Feature Request
|
||||||
|
|
||||||
**Describe the Feature Request**
|
**Describe the Feature Request**
|
||||||
|
|
||||||
<!-- A clear and concise description of what the feature request is. Please include if your feature request is related to a problem. -->
|
<!-- A clear and concise description of what the feature request is. Please include if your feature request is related to a problem. -->
|
||||||
|
|
||||||
**Describe Preferred Solution**
|
**Describe Preferred Solution**
|
||||||
|
|
||||||
<!-- A clear and concise description of what you want to happen. -->
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
**Describe Alternatives**
|
**Describe Alternatives**
|
||||||
|
|
||||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
**Related Code**
|
**Related Code**
|
||||||
|
<!-- If you are able to illustrate the bug or feature request with an example, please provide it here -->
|
||||||
<!-- If you are able to illustrate the bug or feature request with an example, please provide it here. -->
|
|
||||||
|
|
||||||
**Additional Context**
|
**Additional Context**
|
||||||
|
|
||||||
<!-- List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to add, use case, Stack Overflow links, forum links, screenshots, OS if applicable, etc. -->
|
<!-- List any other information that is relevant to your issue. Stack traces, related issues, suggestions on how to add, use case, Stack Overflow links, forum links, screenshots, OS if applicable, etc. -->
|
||||||
|
|
||||||
**If the feature request is approved, would you be willing to submit a PR?**
|
**If the feature request is approved, would you be willing to submit a PR?**
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
---
|
|
||||||
name: Codebase improvement
|
|
||||||
about: Provide your feedback for the existing codebase. Suggest a better solution for algorithms, development tools, etc.
|
|
||||||
title: "dev: "
|
|
||||||
labels: "enhancement"
|
|
||||||
assignees: ""
|
|
||||||
---
|
|
9
.github/ISSUE_TEMPLATE/03_SUPPORT_QUESTION.md
vendored
Normal file
9
.github/ISSUE_TEMPLATE/03_SUPPORT_QUESTION.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
name: Support Question
|
||||||
|
about: Question on how to use this project
|
||||||
|
title: 'support: '
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
# Support Question
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +1 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
|
||||||
- name: ZeroUI Community Support
|
|
||||||
url: https://github.com/dec0dOS/zero-ui/discussions
|
|
||||||
about: Please ask and answer questions here.
|
|
||||||
|
|
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,40 +0,0 @@
|
||||||
<!--- Please provide a general summary of your changes in the title above -->
|
|
||||||
|
|
||||||
## Pull Request type
|
|
||||||
|
|
||||||
<!-- Please try to limit your pull request to one type; submit multiple pull requests if needed. -->
|
|
||||||
|
|
||||||
Please check the type of change your PR introduces:
|
|
||||||
|
|
||||||
- [ ] Bugfix
|
|
||||||
- [ ] Feature
|
|
||||||
- [ ] Code style update (formatting, renaming)
|
|
||||||
- [ ] Refactoring (no functional changes, no API changes)
|
|
||||||
- [ ] Build-related changes
|
|
||||||
- [ ] Documentation content changes
|
|
||||||
- [ ] Other (please describe):
|
|
||||||
|
|
||||||
## What is the current behavior?
|
|
||||||
|
|
||||||
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
|
||||||
|
|
||||||
Issue Number: N/A
|
|
||||||
|
|
||||||
## What is the new behavior?
|
|
||||||
|
|
||||||
<!-- Please describe the behavior or changes that are being added by this PR. -->
|
|
||||||
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-
|
|
||||||
|
|
||||||
## Does this introduce a breaking change?
|
|
||||||
|
|
||||||
- [ ] Yes
|
|
||||||
- [ ] No
|
|
||||||
|
|
||||||
<!-- If this does introduce a breaking change, please describe the impact and migration path for existing applications below. -->
|
|
||||||
|
|
||||||
## Other information
|
|
||||||
|
|
||||||
<!-- Any other information that is important to this PR, such as screenshots of how the component looks before and after the change. -->
|
|
72
.github/labels.yml
vendored
72
.github/labels.yml
vendored
|
@ -1,72 +0,0 @@
|
||||||
---
|
|
||||||
- name: "breaking-change"
|
|
||||||
color: ee0701
|
|
||||||
description: "A change that changes the API or breaks backward compatibility for users."
|
|
||||||
- name: "bug"
|
|
||||||
color: ee0701
|
|
||||||
description: "Inconsistencies or issues which will cause a problem for users or implementors."
|
|
||||||
- name: "documentation"
|
|
||||||
color: 0052cc
|
|
||||||
description: "Solely about the documentation of the project."
|
|
||||||
- name: "enhancement"
|
|
||||||
color: 1d76db
|
|
||||||
description: "Enhancement of the code, not introducing new features."
|
|
||||||
- name: "refactor"
|
|
||||||
color: 1d76db
|
|
||||||
description: "Updating the code with simpler or more efficient syntax or methods. Not introducing new features."
|
|
||||||
- name: "performance"
|
|
||||||
color: 1d76db
|
|
||||||
description: "Improving performance of the project, not introducing new features."
|
|
||||||
- name: "new-feature"
|
|
||||||
color: 0e8a16
|
|
||||||
description: "New features or options."
|
|
||||||
- name: "maintenance"
|
|
||||||
color: 2af79e
|
|
||||||
description: "Generic maintenance tasks."
|
|
||||||
- name: "ci"
|
|
||||||
color: 1d76db
|
|
||||||
description: "Work that improves the continuous integration."
|
|
||||||
- name: "dependencies"
|
|
||||||
color: 1d76db
|
|
||||||
description: "Change in project dependencies."
|
|
||||||
|
|
||||||
- name: "in-progress"
|
|
||||||
color: fbca04
|
|
||||||
description: "Issue is currently being worked on by a developer."
|
|
||||||
|
|
||||||
- name: "security"
|
|
||||||
color: ee0701
|
|
||||||
description: "Addressing a vulnerability or security risk in this project."
|
|
||||||
- name: "incomplete"
|
|
||||||
color: fef2c0
|
|
||||||
description: "Missing information."
|
|
||||||
- name: "invalid"
|
|
||||||
color: fef2c0
|
|
||||||
description: "This is off-topic, spam, or otherwise doesn't apply to this project."
|
|
||||||
|
|
||||||
- name: "beginner-friendly"
|
|
||||||
color: 0e8a16
|
|
||||||
description: "Good first issue for people wanting to contribute to this project."
|
|
||||||
- name: "help-wanted"
|
|
||||||
color: 0e8a16
|
|
||||||
description: "We need some extra helping hands or expertise in order to resolve this!"
|
|
||||||
|
|
||||||
- name: "priority-critical"
|
|
||||||
color: ee0701
|
|
||||||
description: "Must be addressed as soon as possible."
|
|
||||||
- name: "priority-high"
|
|
||||||
color: b60205
|
|
||||||
description: "After critical issues are fixed, these should be dealt with before any further issues."
|
|
||||||
- name: "priority-medium"
|
|
||||||
color: 0e8a16
|
|
||||||
description: "This issue may be useful, and needs some attention."
|
|
||||||
- name: "priority-low"
|
|
||||||
color: e4ea8a
|
|
||||||
description: "Nice addition, maybe... someday..."
|
|
||||||
|
|
||||||
- name: "major"
|
|
||||||
color: b60205
|
|
||||||
description: "This PR causes a major bump in the version number."
|
|
||||||
- name: "minor"
|
|
||||||
color: 0e8a16
|
|
||||||
description: "This PR causes a minor bump in the version number."
|
|
36
.github/workflows/codeql-analysis.yml
vendored
36
.github/workflows/codeql-analysis.yml
vendored
|
@ -1,36 +0,0 @@
|
||||||
name: CodeQL
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*.*.*"
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
schedule:
|
|
||||||
- cron: "30 2 * * 6"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: ["javascript"]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v2
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
21
.github/workflows/labels.yml
vendored
21
.github/workflows/labels.yml
vendored
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
name: Sync labels
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- .github/labels.yml
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
labels:
|
|
||||||
name: ♻️ Sync labels
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: ⤵️ Check out code from GitHub
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: 🚀 Run Label Syncer
|
|
||||||
uses: micnncim/action-label-syncer@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
52
.github/workflows/main.yml
vendored
52
.github/workflows/main.yml
vendored
|
@ -1,52 +0,0 @@
|
||||||
name: CI to Docker Hub
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*.*.*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Prepare zero-ui
|
|
||||||
id: prep_zero-ui
|
|
||||||
run: |
|
|
||||||
DOCKER_IMAGE=${{ secrets.DOCKER_HUB_USERNAME }}/zero-ui
|
|
||||||
VERSION=edge
|
|
||||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
|
||||||
VERSION=${GITHUB_REF#refs/tags/v}
|
|
||||||
fi
|
|
||||||
TAGS="${DOCKER_IMAGE}:${VERSION}"
|
|
||||||
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
|
||||||
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
|
|
||||||
fi
|
|
||||||
echo ::set-output name=tags::${TAGS}
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push zero-ui
|
|
||||||
id: docker_build_zero-ui
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: ./
|
|
||||||
file: ./docker/zero-ui/Dockerfile
|
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.prep_zero-ui.outputs.tags }}
|
|
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -150,9 +150,15 @@ sketch
|
||||||
|
|
||||||
.yarn/*
|
.yarn/*
|
||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/plugins
|
!.yarn/plugins
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/vscode,yarn,react,node
|
# if you are NOT using Zero-installs, then:
|
||||||
|
# comment the following lines
|
||||||
|
!.yarn/cache
|
||||||
|
|
||||||
|
# and uncomment the following lines
|
||||||
|
# .pnp.*
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/vscode,yarn,react,node
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
yarn commitlint --edit $1
|
yarn commitlint --edit
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
yarn lint-staged
|
|
|
@ -1,3 +0,0 @@
|
||||||
frontend/build
|
|
||||||
.yarn
|
|
||||||
tmp
|
|
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
enableGlobalCache: true
|
|
||||||
|
|
||||||
enableTelemetry: false
|
|
||||||
|
|
||||||
nodeLinker: node-modules
|
|
165
CHANGELOG.md
165
CHANGELOG.md
|
@ -2,169 +2,6 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
### [1.5.8](https://github.com/dec0dOS/zero-ui/compare/v1.5.7...v1.5.8) (2023-10-04)
|
## 1.0.0 (2021-03-21)
|
||||||
|
|
||||||
### [1.5.7](https://github.com/dec0dOS/zero-ui/compare/v1.5.6...v1.5.7) (2023-10-04)
|
|
||||||
|
|
||||||
### [1.5.6](https://github.com/dec0dOS/zero-ui/compare/v1.5.5...v1.5.6) (2023-10-04)
|
|
||||||
|
|
||||||
### [1.5.5](https://github.com/dec0dOS/zero-ui/compare/v1.5.4...v1.5.5) (2023-10-04)
|
|
||||||
|
|
||||||
### [1.5.4](https://github.com/dec0dOS/zero-ui/compare/v1.5.3...v1.5.4) (2023-10-04)
|
|
||||||
|
|
||||||
### [1.5.3](https://github.com/dec0dOS/zero-ui/compare/v1.5.2...v1.5.3) (2023-10-04)
|
|
||||||
|
|
||||||
### [1.5.2](https://github.com/dec0dOS/zero-ui/compare/v1.5.1...v1.5.2) (2023-10-04)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- fix network member api resp handling ([856682b](https://github.com/dec0dOS/zero-ui/commit/856682bad1ccd46970681e45bea8a992043c38f4))
|
|
||||||
- ping peer response handling ([db8f497](https://github.com/dec0dOS/zero-ui/commit/db8f4979e65d23d93de99ffa428c9b9a3d3fd952))
|
|
||||||
- revert fix for 1.12.0 ([5d041f6](https://github.com/dec0dOS/zero-ui/commit/5d041f6db63345950cb5782d586c71e0402b7ce7))
|
|
||||||
|
|
||||||
### [1.5.1](https://github.com/dec0dOS/zero-ui/compare/v1.5.0...v1.5.1) (2022-10-09)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **backend/app.js:** fix internal error handler ([15e4051](https://github.com/dec0dOS/zero-ui/commit/15e405162590b2e79dfc32751625f5425613bc52))
|
|
||||||
- **backend/services/member.js:** correctly delete members with unset additionalData ([450a6ad](https://github.com/dec0dOS/zero-ui/commit/450a6ad19414723ce00c48caba98743143a3041f))
|
|
||||||
|
|
||||||
## [1.5.0](https://github.com/dec0dOS/zero-ui/compare/v1.4.1...v1.5.0) (2022-08-25)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **backend:** add cron and ping members ([d3fdac6](https://github.com/dec0dOS/zero-ui/commit/d3fdac61bdd95c7ff42e7db373cd3973d42ca8ce))
|
|
||||||
- last online ([40f98cc](https://github.com/dec0dOS/zero-ui/commit/40f98cc9df322f2b8b4c4a8baed96c96fd2c56d7))
|
|
||||||
|
|
||||||
### [1.4.1](https://github.com/dec0dOS/zero-ui/compare/v1.4.0...v1.4.1) (2022-06-22)
|
|
||||||
|
|
||||||
## [1.4.0](https://github.com/dec0dOS/zero-ui/compare/v1.3.2...v1.4.0) (2022-06-18)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- support http basic auth ([2396e97](https://github.com/dec0dOS/zero-ui/commit/2396e973dc4e40f247cb5fef75d0403ccf0a285a))
|
|
||||||
|
|
||||||
### [1.3.2](https://github.com/dec0dOS/zero-ui/compare/v1.3.1...v1.3.2) (2022-06-18)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- better zt controller error handling ([d7f2e15](https://github.com/dec0dOS/zero-ui/commit/d7f2e153286f9e1dacf4d9fa993321fd6fbc3836))
|
|
||||||
- correct conditional for enabling bearer token ([f30dec6](https://github.com/dec0dOS/zero-ui/commit/f30dec6eacfe0d2ac0031861b4f22f34dbab32c7))
|
|
||||||
- disable authentication properly ([75933d7](https://github.com/dec0dOS/zero-ui/commit/75933d7e59838f7c8728ca08cf39659f24a6cac6))
|
|
||||||
- simplify code and check login status on home page load ([ddb3f44](https://github.com/dec0dOS/zero-ui/commit/ddb3f442f85991db4fa0721f0d7c2b004a9ea12d))
|
|
||||||
- stop redundant fetching /auth/login ([ce9f794](https://github.com/dec0dOS/zero-ui/commit/ce9f7943c04d117b0ace3025cd9f84d7b14cf5f3))
|
|
||||||
- update disableAuth in localStorage if server config changes ([036e577](https://github.com/dec0dOS/zero-ui/commit/036e5779ba319a63c9d749c32fcbd5452d2bd2d2))
|
|
||||||
|
|
||||||
### [1.3.1](https://github.com/dec0dOS/zero-ui/compare/v1.3.0...v1.3.1) (2022-06-12)
|
|
||||||
|
|
||||||
## [1.3.0](https://github.com/dec0dOS/zero-ui/compare/v1.2.2...v1.3.0) (2022-05-24)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- disable auth ([#59](https://github.com/dec0dOS/zero-ui/issues/59)) ([e7fb4d0](https://github.com/dec0dOS/zero-ui/commit/e7fb4d0aa84c26493b58a1cd3349fd98a2861191))
|
|
||||||
|
|
||||||
### [1.2.2](https://github.com/dec0dOS/zero-ui/compare/v1.2.1...v1.2.2) (2022-04-26)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- fix crash when network had no IP ranges ([8095d2b](https://github.com/dec0dOS/zero-ui/commit/8095d2bea235e348baf3bac515d8aa9eb7adb8cf))
|
|
||||||
|
|
||||||
### [1.2.1](https://github.com/dec0dOS/zero-ui/compare/v1.2.0...v1.2.1) (2021-12-19)
|
|
||||||
|
|
||||||
## [1.2.0](https://github.com/dec0dOS/zero-ui/compare/v1.1.5...v1.2.0) (2021-12-19)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- update_frontend_online_parsing ([cd6699d](https://github.com/dec0dOS/zero-ui/commit/cd6699d9b7d90c514dd2b870da4b61b8a5ea4ea0))
|
|
||||||
|
|
||||||
### [1.1.5](https://github.com/dec0dOS/zero-ui/compare/v1.1.4...v1.1.5) (2021-12-16)
|
|
||||||
|
|
||||||
### [1.1.4](https://github.com/dec0dOS/zero-ui/compare/v1.1.3...v1.1.4) (2021-12-16)
|
|
||||||
|
|
||||||
### [1.1.3](https://github.com/dec0dOS/zero-ui/compare/v1.1.2...v1.1.3) (2021-12-16)
|
|
||||||
|
|
||||||
### [1.1.2](https://github.com/dec0dOS/zero-ui/compare/v1.1.1...v1.1.2) (2021-12-16)
|
|
||||||
|
|
||||||
### [1.1.1](https://github.com/dec0dOS/zero-ui/compare/v1.1.0...v1.1.1) (2021-12-16)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- migrate to yarn v3 ([781a48d](https://github.com/dec0dOS/zero-ui/commit/781a48d341bf386cfbc917c78789f802227bfdef))
|
|
||||||
|
|
||||||
## [1.1.0](https://github.com/dec0dOS/zero-ui/compare/v1.0.21...v1.1.0) (2021-12-16)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- add capabilities and tags support ([8f89174](https://github.com/dec0dOS/zero-ui/commit/8f891747d6d98c0957405954f50c5bab5d2f9551)), closes [#35](https://github.com/dec0dOS/zero-ui/issues/35)
|
|
||||||
- some improvements in ui for caps and tags ([2f27c11](https://github.com/dec0dOS/zero-ui/commit/2f27c112ea2e05f8ca6de8219179d51261ab721f))
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- fix compatibility for existing networks after supporting tags and capabilities ([a813d05](https://github.com/dec0dOS/zero-ui/commit/a813d05b3c2c2c286ae1df860eca209215347ce0))
|
|
||||||
- fix original managed IP delete ([9386aa7](https://github.com/dec0dOS/zero-ui/commit/9386aa724b67019e0783691f611fc08877cbfe85)), closes [#12](https://github.com/dec0dOS/zero-ui/issues/12)
|
|
||||||
- parse tags and capabilities in flow rules correctly ([369d96e](https://github.com/dec0dOS/zero-ui/commit/369d96e50ab523c85123e6d783c44d012e7756ed))
|
|
||||||
- rename "tag enum id" to "tag value" ([27aa2c5](https://github.com/dec0dOS/zero-ui/commit/27aa2c5d47d99c329d1e80b60a08307e30239db1))
|
|
||||||
|
|
||||||
### [1.0.21](https://github.com/dec0dOS/zero-ui/compare/v1.0.20...v1.0.21) (2021-11-04)
|
|
||||||
|
|
||||||
### [1.0.20](https://github.com/dec0dOS/zero-ui/compare/v1.0.19...v1.0.20) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.19](https://github.com/dec0dOS/zero-ui/compare/v1.0.18...v1.0.19) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.18](https://github.com/dec0dOS/zero-ui/compare/v1.0.17...v1.0.18) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.17](https://github.com/dec0dOS/zero-ui/compare/v1.0.16...v1.0.17) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.16](https://github.com/dec0dOS/zero-ui/compare/v1.0.15...v1.0.16) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.15](https://github.com/dec0dOS/zero-ui/compare/v1.0.14...v1.0.15) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.14](https://github.com/dec0dOS/zero-ui/compare/v1.0.13...v1.0.14) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.13](https://github.com/dec0dOS/zero-ui/compare/v1.0.12...v1.0.13) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.12](https://github.com/dec0dOS/zero-ui/compare/v1.0.11...v1.0.12) (2021-08-24)
|
|
||||||
|
|
||||||
### [1.0.11](https://github.com/dec0dOS/zero-ui/compare/v1.0.10...v1.0.11) (2021-07-08)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- database member data init fix. Thanks [@thelittlerocket](https://github.com/thelittlerocket)" ([92091d9](https://github.com/dec0dOS/zero-ui/commit/92091d9ea52ad3d64a898d8549cd4f185dbe78eb))
|
|
||||||
- listen on ipv4 by default. May prevent issues on VPS with ipv6 enabled ([eb17cab](https://github.com/dec0dOS/zero-ui/commit/eb17cab75443edc5082146eb513615dc58d3f759))
|
|
||||||
|
|
||||||
### [1.0.10](https://github.com/dec0dOS/zero-ui/compare/v1.0.9...v1.0.10) (2021-05-15)
|
|
||||||
|
|
||||||
### [1.0.9](https://github.com/dec0dOS/zero-ui/compare/v1.0.8...v1.0.9) (2021-05-15)
|
|
||||||
|
|
||||||
### [1.0.8](https://github.com/dec0dOS/zero-ui/compare/v1.0.7...v1.0.8) (2021-05-15)
|
|
||||||
|
|
||||||
### [1.0.6](https://github.com/dec0dOS/zero-ui/compare/v1.0.5...v1.0.6) (2021-05-15)
|
|
||||||
|
|
||||||
### [1.0.5](https://github.com/dec0dOS/zero-ui/compare/v1.0.4...v1.0.5) (2021-04-16)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **backend/routes/network.js:** fixes bug after refactor ([112476e](https://github.com/dec0dOS/zero-ui/commit/112476e7fc2850ea7caef9c996d1b2610031395c))
|
|
||||||
|
|
||||||
### [1.0.4](https://github.com/dec0dOS/zero-ui/compare/v1.0.3...v1.0.4) (2021-04-16)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **backend/app.js:** fixes database initialization ([0aa2eed](https://github.com/dec0dOS/zero-ui/commit/0aa2eed17a96f97c42fa1fe953d27d1419ea91e2))
|
|
||||||
|
|
||||||
### [1.0.3](https://github.com/dec0dOS/zero-ui/compare/v1.0.2...v1.0.3) (2021-03-22)
|
|
||||||
|
|
||||||
### [1.0.2](https://github.com/dec0dOS/zero-ui/compare/v1.0.1...v1.0.2) (2021-03-22)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- merge commands in package.json ([6aa6f1d](https://github.com/dec0dOS/zero-ui/commit/6aa6f1d69bd399e985f6a20cd2c79e51a3fd1238))
|
|
||||||
|
|
||||||
### [1.0.1](https://github.com/dec0dOS/zero-ui/compare/v1.0.0...v1.0.1) (2021-03-22)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- preserve data directory in git ([e3f613d](https://github.com/dec0dOS/zero-ui/commit/e3f613ddeb66b6f6b55cbbfd29d88c07df00a598))
|
|
||||||
- tooling config ([548bf76](https://github.com/dec0dOS/zero-ui/commit/548bf764584cca6ba28ea6574d404d77d6ce84fb))
|
|
||||||
|
|
||||||
## 1.0.0 (2021-03-21)
|
## 1.0.0 (2021-03-21)
|
||||||
|
|
5
LICENSE
5
LICENSE
|
@ -631,7 +631,8 @@ to attach them to the start of each source file to most effectively
|
||||||
state the exclusion of warranty; and each file should have at least
|
state the exclusion of warranty; and each file should have at least
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
Copyright (C) 2021 Aliaksei Patapau
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -651,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||||
If the program does terminal interaction, make it output a short
|
If the program does terminal interaction, make it output a short
|
||||||
notice like this when it starts in an interactive mode:
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
ZeroUI Copyright (C) 2021 Aliaksei Patapau
|
<program> Copyright (C) <year> <name of author>
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
This is free software, and you are welcome to redistribute it
|
This is free software, and you are welcome to redistribute it
|
||||||
under certain conditions; type `show c' for details.
|
under certain conditions; type `show c' for details.
|
||||||
|
|
316
README.md
316
README.md
|
@ -1,25 +1,23 @@
|
||||||
|
<!-- PROJECT LOGO -->
|
||||||
<br />
|
<br />
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/dec0dOS/zero-ui">
|
<a href="https://github.com/dec0dOS/zero-ui">
|
||||||
<img src="docs/images/logo.png" alt="Logo" width="150" height="150">
|
<img src="docs/images/logo.png" alt="Logo" width="80" height="80">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
ZeroUI - ZeroTier Controller Web UI - is a web user interface for a self-hosted ZeroTier network controller.
|
ZeroUI - ZeroTier Controller Web UI - is a web user interface for a self-hosted ZeroTier network controller.
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/dec0dOS/zero-ui/blob/main/docs/SCREENSHOTS.md"><strong>Explore the screenshots »</strong></a>
|
<a href="https://github.com/dec0dOS/zero-ui"><strong>Explore the docs »</strong></a>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<a href="https://github.com/dec0dOS/zero-ui/issues">Bug Report</a>
|
<a href="https://github.com/dec0dOS/zero-ui/issues">Report Bug</a>
|
||||||
·
|
·
|
||||||
<a href="https://github.com/dec0dOS/zero-ui/issues">Feature Request</a>
|
<a href="https://github.com/dec0dOS/zero-ui/issues">Request Feature</a>
|
||||||
·
|
|
||||||
<a href="https://github.com/dec0dOS/zero-ui/discussions">Ask a Question</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<details open="open" >
|
<summary><h2 style="display: inline-block">Table of Contents</h2></summary>
|
||||||
<summary>Table of Contents</summary>
|
|
||||||
|
|
||||||
- [About](#about)
|
- [About](#about)
|
||||||
- [Built With](#built-with)
|
- [Built With](#built-with)
|
||||||
|
@ -37,277 +35,205 @@
|
||||||
- [Copyright notice](#copyright-notice)
|
- [Copyright notice](#copyright-notice)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
This project drew inspiration from [ztncui](https://github.com/key-networks/ztncui) and was developed to address the current limitations of self-hosted [network controllers](https://github.com/zerotier/ZeroTierOne/tree/master/controller). Some of the issues in [ztncui](https://github.com/key-networks/ztncui) cannot be resolved due to the core architecture of the project. ZeroUI aims to resolve these issues and introduces the following features:
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
- It is a lightweight [Single Page Application (SPA)](https://en.wikipedia.org/wiki/Single-page_application) built with React, providing an improved user experience, and it is mobile-friendly.
|
This project is highly inspired by [ztncui](https://github.com/key-networks/ztncui) and was developed to address the current limitations of applying the self-hosted [network controllers](https://github.com/zerotier/ZeroTierOne/tree/master/controller). Some [ztncui](https://github.com/key-networks/ztncui) problems cannot be fixed because of the core architecture of the project. ZeroUI tries to solve them and implements the following features:
|
||||||
- ZeroUI is compatible with the ZeroTier Central API, allowing you to use CLI tools and custom applications designed for ZeroTier Central to manage your networks.
|
* Full React-powered lightweight [SPA](https://en.wikipedia.org/wiki/Single-page_application) that brings better user experience, and ZeroUI is mobile-friendly.
|
||||||
- ZeroUI implements controller-specific workarounds to address certain existing [issues](https://github.com/zerotier/ZeroTierOne/issues/859) that are not addressed in [ZTNCUI](https://github.com/key-networks/ztncui/issues/63).
|
* ZeroUI has ZeroTier Central complitible API. That means you could use CLI tools and custom applications made only for ZeroTier Central to manage your networks.
|
||||||
- ZeroUI is more feature-complete, supporting almost all network controller features, including a rule editor. Development is ongoing, so you can expect regular updates with new features and bug fixes.
|
* ZeroUI implements controller-specific workarounds that address some existing [issues](https://github.com/zerotier/ZeroTierOne/issues/859)
|
||||||
- Deploying ZeroUI is straightforward; refer to the [installation](#installation) section for more information.
|
* ZeroUI is more feature complete. ZeroUI has almost all network-controller supported features like rule editor. The development process hasn't stopped, so you will enjoy new features and bug fixes in the near future.
|
||||||
|
* ZeroUI deployment is simple. Please refer to [installation](#installation) for more info.
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Curious about ZeroTier?</summary>
|
<details open>
|
||||||
|
<summary>Wait, I haven't heard about ZeroTier yet...</summary>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
[ZeroTier](https://www.zerotier.com) is an impressive [open-source project](https://github.com/zerotier/ZeroTierOne) available on a wide range of [platforms](https://www.zerotier.com/download/). It can resolve many of your complex networking issues, potentially replacing your intricate VPN setups. You can create a virtual LAN and manage all your devices effortlessly.
|
[ZeroTier](https://www.zerotier.com) is awesome [open source project](https://github.com/zerotier/ZeroTierOne) that is avalible on wide range of [platforms](https://www.zerotier.com/download/).
|
||||||
|
Most of your hard networking problems could be solved with ZeroTier. It could replace all your complex VPN setups. You can place all your devices on a virtual LAN and manage it easily.
|
||||||
In essence, ZeroTier combines the capabilities of VPN and SD-WAN, simplifying network management.
|
|
||||||
|
|
||||||
|
To sum up, ZeroTier combines the capabilities of VPN and SD-WAN, simplifying network management.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Built With
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
### Built With
|
||||||
|
|
||||||
Frontend:
|
Frontend:
|
||||||
|
|
||||||
- [React](https://reactjs.org)
|
- [React](https://reactjs.org)
|
||||||
- [Material UI](https://material-ui.com)
|
- [Material UI](https://material-ui.com)
|
||||||
|
|
||||||
Backend:
|
Backend:
|
||||||
|
|
||||||
- [NodeJS](https://nodejs.org)
|
- [NodeJS](https://nodejs.org)
|
||||||
- [Express](https://expressjs.com)
|
- [Express](https://expressjs.com)
|
||||||
- [Lowdb](https://github.com/typicode/lowdb)
|
- [Lowdb](https://github.com/typicode/lowdb)
|
||||||
|
|
||||||
Ready-to-use deployment solution:
|
Deploy:
|
||||||
|
|
||||||
- [Docker](https://www.docker.com)
|
- [Docker](https://www.docker.com)
|
||||||
- [Docker Compose](https://docs.docker.com/compose/)
|
- [Docker Compose](https://docs.docker.com/compose/)
|
||||||
- [Caddy](https://caddyserver.com)
|
- [Caddy](https://caddyserver.com)
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
The recommended way to install ZeroUI is by using Docker and Docker Compose. To install [Docker](https://docs.docker.com/get-docker) and [Docker Compose](https://docs.docker.com/compose/install) on your system, please follow the installation guide in the [official Docker documentation](https://docs.docker.com/get-docker).
|
The recommended method to install ZeroUI is by using Docker and Docker Compose.
|
||||||
|
To install [Docker](https://docs.docker.com/get-docker) and [Docker Compose](https://docs.docker.com/compose/install) on your system, please follow the installation guide from the [official Docker documentation](https://docs.docker.com/get-docker).
|
||||||
|
|
||||||
For HTTPS setup, you will need a domain name. You can obtain one for free at https://www.duckdns.org.
|
For HTTPS setup you will need a domain name.
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
Here's a straightforward one-minute installation guide, perfect for a fresh VPS setup:
|
The most simple one-minute installation. Great for the fresh VPS setup.
|
||||||
|
|
||||||
1. Create a project directory
|
1. Download the `docker-compose.yml` file
|
||||||
|
```sh
|
||||||
|
wget https://raw.githubusercontent.com/dec0dOS/zero-ui/main/docker-compose.yml
|
||||||
|
```
|
||||||
|
2. Replace `example.com` with your domain name in `docker-compose.yml`
|
||||||
|
3. Pull the images
|
||||||
|
```sh
|
||||||
|
docker-compose pull
|
||||||
|
```
|
||||||
|
4. Run the containers
|
||||||
|
```sh
|
||||||
|
docker-compose up -d --no-build
|
||||||
|
```
|
||||||
|
5. Check if everything is okay
|
||||||
|
```sh
|
||||||
|
docker-compose logs
|
||||||
|
```
|
||||||
|
6. Disable your firewall for the following ports: `80/tcp`, `443/tcp` and `9993/udp`
|
||||||
|
* on ubuntu/debian with ufw installed:
|
||||||
|
```sh
|
||||||
|
ufw allow 80/tcp
|
||||||
|
ufw allow 443/tcp
|
||||||
|
ufw allow 9993/udp
|
||||||
|
```
|
||||||
|
* or you may use the old good iptables:
|
||||||
|
```sh
|
||||||
|
iptables -I INPUT 6 -m state --state NEW -p tcp --dport 80 -j ACCEPT
|
||||||
|
iptables -I INPUT 6 -m state --state NEW -p tcp --dport 443 -j ACCEPT
|
||||||
|
iptables -I INPUT 6 -m state --state NEW -p udp --dport 9993 -j ACCEPT
|
||||||
|
```
|
||||||
|
7. Navigate to `https://YOURDOMAIN.com/app/`.
|
||||||
|
Now you could use your ZeroUI instance with HTTPS support and automated certificate renewal.
|
||||||
|
|
||||||
```sh
|
> To disable HTTPS, please remove https-proxy from `docker-compose.yml`, set `ZU_SECURE_HEADERS` to `false` and change zero-ui port `expose` to `ports`.
|
||||||
mkdir -p /srv/zero-ui/
|
|
||||||
cd /srv/zero-ui/
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Download the `docker-compose.yml` file
|
|
||||||
|
|
||||||
```sh
|
|
||||||
wget https://raw.githubusercontent.com/dec0dOS/zero-ui/main/docker-compose.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -L -O https://raw.githubusercontent.com/dec0dOS/zero-ui/main/docker-compose.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Replace `YOURDOMAIN.com` with your domain name and set admin credentials (`ZU_DEFAULT_PASSWORD`) in `docker-compose.yml`
|
|
||||||
4. Pull the image
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker pull dec0dos/zero-ui
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Run the containers
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker-compose up -d --no-build
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Check if everything is okay (`CTRL-C` to stop log preview)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker-compose logs -f
|
|
||||||
```
|
|
||||||
|
|
||||||
7. Disable your firewall for the following ports: `80/tcp`, `443/tcp`, and `9993/udp`
|
|
||||||
|
|
||||||
- On Ubuntu/Debian with ufw installed:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
ufw allow 80/tcp
|
|
||||||
ufw allow 443/tcp
|
|
||||||
ufw allow 9993/udp
|
|
||||||
```
|
|
||||||
|
|
||||||
- Or you can use iptables:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
|
|
||||||
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
|
|
||||||
iptables -A INPUT -p udp --dport 9993 -j ACCEPT
|
|
||||||
```
|
|
||||||
|
|
||||||
8. Navigate to `https://YOURDOMAIN.com/app/`.
|
|
||||||
Now you can use your ZeroUI instance with HTTPS support and automated certificate renewal.
|
|
||||||
|
|
||||||
> To disable Caddy proxy and HTTPS, remove the `https-proxy` from `docker-compose.yml`, set `ZU_SECURE_HEADERS` to `false`, and change zero-ui port `expose` to `ports`.
|
|
||||||
|
|
||||||
Advanced manual setups are also supported. Check the following environment variables as a reference:
|
Advanced manual setups are also supported. Check the following environment variables as a reference:
|
||||||
| Name | Default value | Description |
|
| Name | Default value | Description |
|
||||||
| ---------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
| ---------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||||
| NODE_ENV | unset | You could learn more [here](https://nodejs.dev/learn/nodejs-the-difference-between-development-and-production) |
|
| NODE_ENV | unset | You could learn more [here](https://nodejs.dev/learn/nodejs-the-difference-between-development-and-production) |
|
||||||
| LISTEN_ADDRESS | `0.0.0.0` | Express server listen address |
|
| ZU_SERVE_FRONTEND | true | You could disable frontend serving and use ZeroUI instance as REST API for your ZeroTier controller |
|
||||||
| ZU_SERVE_FRONTEND | `true` | You could disable frontend serving and use ZeroUI instance as REST API for your ZeroTier controller |
|
| ZU_SECURE_HEADERS | true | Enables [helmet](https://helmetjs.github.io) |
|
||||||
| ZU_SECURE_HEADERS | `true` | Enables [helmet](https://helmetjs.github.io) |
|
| ZU_CONTROLLER_ENDPOINT | http://localhost:9993/ | ZeroTier controller API endpoint |
|
||||||
| ZU_CONTROLLER_ENDPOINT | `http://localhost:9993/` | ZeroTier controller API endpoint |
|
| ZU_CONTROLLER_TOKEN | from /var/lib/zerotier-one/authtoken.secret | ZeroTier controller API token |
|
||||||
| ZU_CONTROLLER_TOKEN | from `/var/lib/zerotier-one/authtoken.secret` | ZeroTier controller API token |
|
| ZU_DEFAULT_USERNAME | unset (docker-compose.yml: admin) | Default username that will be set on the first run |
|
||||||
| ZU_DEFAULT_USERNAME | unset (`docker-compose.yml`: admin) | Default username that will be set on the first run |
|
| ZU_DEFAULT_PASSWORD | unset (docker-compose.yml: zero-ui) | Default password that will be set on the first run |
|
||||||
| ZU_DEFAULT_PASSWORD | unset (`docker-compose.yml`: zero-ui) | Default password that will be set on the first run |
|
| ZU_DATAPATH | data/db.json | ZeroUI data storage path |
|
||||||
| ZU_DATAPATH | `data/db.json` | ZeroUI data storage path |
|
|
||||||
| ZU_DISABLE_AUTH | `false` | If set to true, automatically log in all users. This is useful if ZeroUI is protected by an authentication proxy. Note that when this value is changed, the localStorage of instances of logged-in panels should be cleared |
|
|
||||||
| ZU_LAST_SEEN_FETCH | `true`| Enables [Last Seen feature](https://github.com/dec0dOS/zero-ui/issues/40) |
|
|
||||||
| ZU_LAST_SEEN_SCHEDULE | `*/5 * * * *` | Last Seen cron-like schedule |
|
|
||||||
| ZU_LOGIN_LIMIT | `false` | Enable rate limiter for /login endpoint |
|
|
||||||
| ZU_LOGIN_LIMIT_WINDOW | 30 | The duration of the IP ban in minutes |
|
|
||||||
| ZU_LOGIN_LIMIT_ATTEMPTS | 50 | Login attemps before ban |
|
|
||||||
|
|
||||||
ZeroUI could be deployed as a regular nodejs web application, but it requires a ZeroTier controller that is installed with the `zerotier-one` package. For more info about the network controller, you could read [here](https://github.com/zerotier/ZeroTierOne/tree/master/controller/#readme).
|
ZeroUI could be deployed as a regular nodejs web application, but it requires ZeroTier controller that is installed with `zerotier-one` package. More info about the network controller you could read [here](https://github.com/zerotier/ZeroTierOne/tree/master/controller)
|
||||||
|
|
||||||
For Ansible Role, please refer to the [zero-ui-ansible repository](https://github.com/dec0dOS/zero-ui-ansible).
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Controller Setup Tips (Outside Docker)</summary>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
If you are using an existing controller on the host, you may need to allow connections from the Docker container. There are two ways to do this:
|
|
||||||
|
|
||||||
1. Allow controller management from any IP address:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
echo "{\"settings\": {\"portMappingEnabled\": true,\"softwareUpdate\": \"disable\",\"allowManagementFrom\": [\"0.0.0.0/0\"]}}" > /var/lib/zerotier-one/local.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
> Warning: Don't forget to block connections to 9993/TCP from the WAN. Directly exposing the controller API to the WAN is not recommended; it should be proxified via the ZeroUI backend.
|
|
||||||
|
|
||||||
2. Add `network_mode: "host"` to zero-ui in `docker-compose.yml`.
|
|
||||||
|
|
||||||
For more information, please refer to this [discussion](https://github.com/dec0dOS/zero-ui/discussions/8).
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
After installation, log in with the credentials declared with `ZU_DEFAULT_USERNAME` and `ZU_DEFAULT_PASSWORD`.
|
After installation, log in with your credentials that are declared with ZU_DEFAULT_USERNAME and ZU_DEFAULT_PASSWORD.
|
||||||
|
|
||||||
Currently, some main ZeroTier Central features are missing. Refer to the [roadmap](#roadmap) for more information.
|
Currently, almost all actions are available through the UI. Refer to the [roadmap](#roadmap) for more information.
|
||||||
|
|
||||||
_For screenshots, please refer to the [screenshots](docs/SCREENSHOTS.md) section._
|
_For the screenshots, please refer to the [screenshots](docs/SCREENSHOTS.md)_
|
||||||
|
|
||||||
### Update
|
### Update
|
||||||
|
To get the latest version just run
|
||||||
|
|
||||||
To get the latest version, simply run
|
docker-compose down && docker-compose pull && docker-compose up -d --no-build
|
||||||
|
|
||||||
```sh
|
in the folder where `docker-compose.yml` is located. Backup is not required as your data is saved in Docker volumes but recommended.
|
||||||
docker-compose pull && docker-compose up -d --no-build
|
|
||||||
```
|
|
||||||
|
|
||||||
in the folder where `docker-compose.yml` is located. Backups may not be necessary since most of your data is usually saved at the controller level, but it's still a good idea to consider them as a precautionary measure.
|
|
||||||
|
|
||||||
### Backup
|
### Backup
|
||||||
|
The easiest way to create your ZeroUI data backup is to use the following commands:
|
||||||
|
|
||||||
You should regularly back up the `zerotier-one` and `data` folders in your ZeroUI installation directory. You can do this manually before upgrading using the following commands:
|
docker run --rm --volumes-from zu-controller -v $(pwd):/backup ubuntu tar cvf /backup/backup-controller.tar /var/lib/zerotier-one
|
||||||
|
docker run --rm --volumes-from zu-main -v $(pwd):/backup ubuntu tar cvf /backup/backup-ui.tar /app/backend/data
|
||||||
|
|
||||||
```sh
|
|
||||||
tar cvf backup-ui.tar data/
|
|
||||||
tar cvf backup-zt.tar zerotier-one/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
For a list of proposed features (and known issues), see the [open issues](https://github.com/dec0dOS/zero-ui/issues).
|
See the [open issues](https://github.com/dec0dOS/zero-ui/issues) for a list of proposed features (and known issues).
|
||||||
|
|
||||||
- [Top Feature Requests](https://github.com/dec0dOS/zero-ui/issues?q=label%3Aenhancement+is%3Aopen+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction)
|
|
||||||
- [Top Bugs](https://github.com/dec0dOS/zero-ui/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) (Add your votes using the 👍 reaction)
|
|
||||||
- [Newest Bugs](https://github.com/dec0dOS/zero-ui/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
|
|
||||||
|
|
||||||
[](https://github.com/dec0dOS/zero-ui/issues)
|
|
||||||
|
|
||||||
When creating bug reports, please ensure they are:
|
|
||||||
|
|
||||||
- _Reproducible._ Include steps to reproduce the problem.
|
|
||||||
- _Specific._ Provide as much detail as possible, including version, environment, etc.
|
|
||||||
- _Unique._ Avoid duplicating existing open issues.
|
|
||||||
- _Scoped to a Single Bug._ Report one bug per issue.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Firstly, thank you for considering contributing! Contributions are what make the open-source community thrive. Any contributions you make will benefit everyone, and they are highly appreciated.
|
Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are **greatly appreciated**.
|
||||||
|
|
||||||
To contribute:
|
1. Fork the project
|
||||||
|
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
||||||
|
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
||||||
|
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
||||||
|
5. Open a pull request
|
||||||
|
|
||||||
1. Fork the project.
|
ZeroUI uses [conventional commits](https://www.conventionalcommits.org), so please follow the guidelines.
|
||||||
2. Create your feature branch (`git checkout -b feat/amazing_feature`).
|
|
||||||
3. Commit your changes (`git commit -m 'feat: add amazing_feature'`).
|
|
||||||
4. Push to the branch (`git push origin feat/amazing_feature`).
|
|
||||||
5. [Open a Pull Request](https://github.com/dec0dOS/zero-ui/compare?expand=1)
|
|
||||||
|
|
||||||
ZeroUI uses [conventional commits](https://www.conventionalcommits.org), so please follow the guidelines. You can use `yarn commit` to open a [Text-Based User Interface (TUI)](https://en.wikipedia.org/wiki/Text-based_user_interface) that follows conventional commits guidelines.
|
### Development environment
|
||||||
|
|
||||||
### Development Environment
|
To set up a development environment, please follow these steps:
|
||||||
|
|
||||||
To set up a development environment, follow these steps:
|
|
||||||
|
|
||||||
1. Clone the repo
|
1. Clone the repo
|
||||||
|
```sh
|
||||||
```sh
|
git clone https://github.com/dec0dOS/zero-ui.git
|
||||||
git clone https://github.com/dec0dOS/zero-ui.git
|
```
|
||||||
cd zero-ui
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install packages
|
2. Install packages
|
||||||
|
```sh
|
||||||
```sh
|
yarn installDeps
|
||||||
yarn install
|
```
|
||||||
```
|
|
||||||
|
|
||||||
3. Start the development server
|
3. Start the development server
|
||||||
|
```sh
|
||||||
```sh
|
yarn dev
|
||||||
yarn dev
|
```
|
||||||
```
|
|
||||||
|
|
||||||
4. Navigate to http://localhost:3000
|
4. Navigate to http://localhost:3000
|
||||||
|
|
||||||
You will also need to install the ZeroTier controller. On Linux, installing the `zerotier-one` package is sufficient, but other platforms may require some adjustments. First, you should obtain the controller token. On macOS, you can find it using the following command:
|
It is also required to install ZeroTier controller. On Linux installing `zerotier-one` package is enough, other platforms require some tweaking. Firstly you should get the controller token. On macOS, you could find it with the following command:
|
||||||
|
|
||||||
```sh
|
sudo cat "/Library/Application Support/ZeroTier/One/authtoken.secret"
|
||||||
sudo cat "/Library/Application Support/ZeroTier/One/authtoken.secret"
|
|
||||||
```
|
|
||||||
|
|
||||||
Afterward, you can start the ZeroUI development environment:
|
After you could start ZeroUI development environment:
|
||||||
|
|
||||||
```sh
|
ZU_CONTROLLER_TOKEN=TOKEN_FROM_authtoken.secret yarn dev
|
||||||
ZU_CONTROLLER_TOKEN=TOKEN_FROM_authtoken.secret yarn dev
|
|
||||||
```
|
|
||||||
|
|
||||||
For other platforms, please refer to the [ZeroTier manual](https://docs.zerotier.com/service/v1/).
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
If you need assistance or have questions, reach out through [GitHub Discussions](https://github.com/dec0dOS/zero-ui/discussions).
|
Reach out to me at one of the following places:
|
||||||
|
|
||||||
|
- Telegram: ***REMOVED***
|
||||||
|
- E-Mail: *****REMOVED*****
|
||||||
|
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
ZeroUI follows best practices for security, but complete security cannot be guaranteed. ZeroUI is provided "as is" without any warranty. Use at your own risk.
|
ZeroUI follows good practices of security, but 100% security can't be granted in software. ZeroUI is provided "as is" without any warranty. Use at your own risk.
|
||||||
|
|
||||||
For enterprise support and a more reliable and scalable solution, please consider using ZeroTier Central.
|
For enterprise support, a more reliable and scalable solution, please use ZeroTier Central.
|
||||||
|
|
||||||
For more information and to report security issues, please refer to our [security documentation](docs/SECURITY.md).
|
_For more info, please refer to the [security](docs/SECURITY.md)_
|
||||||
|
|
||||||
## Copyright Notice
|
|
||||||
|
|
||||||
ZeroUI is not affiliated with, associated with, or endorsed by ZeroTier Central or ZeroTier, Inc.
|
## Copyright notice
|
||||||
|
|
||||||
|
ZeroUI is not affiliated or associated with or endorsed by ZeroTier Central or ZeroTier, Inc.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
{
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint",
|
|
||||||
"unicorn",
|
|
||||||
"jsdoc",
|
|
||||||
"import",
|
|
||||||
"promise",
|
|
||||||
"sonarjs"
|
|
||||||
],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:n/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:unicorn/recommended",
|
|
||||||
"plugin:jsdoc/recommended",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:promise/recommended",
|
|
||||||
"plugin:sonarjs/recommended",
|
|
||||||
"plugin:security/recommended"
|
|
||||||
],
|
|
||||||
"root": true,
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"project": "./tsconfig.json",
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
"varsIgnorePattern": "^_",
|
|
||||||
"ignoreRestSiblings": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-misused-promises": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"checksVoidReturn": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
"jsdoc/require-jsdoc": ["warn", { "publicOnly": true }],
|
|
||||||
"jsdoc/require-description": "off",
|
|
||||||
"import/no-unresolved": "off",
|
|
||||||
"unicorn/no-empty-file": "off",
|
|
||||||
"unicorn/consistent-function-scoping": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"checkArrowFunctions": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"unicorn/prefer-module": "off",
|
|
||||||
"unicorn/prevent-abbreviations": "off",
|
|
||||||
"unicorn/catch-error-name": "off",
|
|
||||||
"unicorn/prefer-ternary": "off",
|
|
||||||
"unicorn/prefer-event-target": "off",
|
|
||||||
"security/detect-object-injection": "off",
|
|
||||||
"security/detect-non-literal-fs-filename": "off"
|
|
||||||
}
|
|
||||||
}
|
|
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
|
@ -1,5 +1,5 @@
|
||||||
# Data
|
# Data
|
||||||
data/db.json
|
data
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
|
|
@ -1,44 +1,33 @@
|
||||||
import path from "path";
|
const express = require("express");
|
||||||
import * as url from "url";
|
const path = require("path");
|
||||||
import express from "express";
|
const logger = require("morgan");
|
||||||
import logger from "morgan";
|
const compression = require("compression");
|
||||||
import compression from "compression";
|
const bearerToken = require("express-bearer-token");
|
||||||
import bearerToken from "express-bearer-token";
|
const helmet = require("helmet");
|
||||||
import helmet from "helmet";
|
|
||||||
import { Cron } from "croner";
|
|
||||||
|
|
||||||
import { db } from "./utils/db.js";
|
const db = require("./utils/db");
|
||||||
import { initAdmin } from "./utils/init-admin.js";
|
const initAdmin = require("./utils/init-admin");
|
||||||
import { pingAll } from "./utils/ping.js";
|
|
||||||
|
|
||||||
import authRoutes from "./routes/auth.js";
|
const authRoutes = require("./routes/auth");
|
||||||
import networkRoutes from "./routes/network.js";
|
const networkRoutes = require("./routes/network");
|
||||||
import memberRoutes from "./routes/member.js";
|
const memberRoutes = require("./routes/member");
|
||||||
import controllerRoutes from "./routes/controller.js";
|
const controllerRoutes = require("./routes/controller");
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
||||||
|
|
||||||
app.use(logger("dev"));
|
app.use(logger("dev"));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: false }));
|
app.use(express.urlencoded({ extended: false }));
|
||||||
if (process.env.ZU_DISABLE_AUTH !== "true") {
|
app.use(
|
||||||
app.use(
|
bearerToken({
|
||||||
bearerToken({
|
headerKey: "Bearer",
|
||||||
headerKey: "token",
|
})
|
||||||
})
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
|
||||||
console.debug = function () {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV === "production" &&
|
process.env.NODE_ENV === "production" &&
|
||||||
process.env.ZU_SECURE_HEADERS !== "false"
|
process.env.ZU_SECURE_HEADERS !== "false"
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +40,6 @@ if (
|
||||||
["/app", "/app/*"],
|
["/app", "/app/*"],
|
||||||
express.static(path.join(__dirname, "..", "frontend", "build"))
|
express.static(path.join(__dirname, "..", "frontend", "build"))
|
||||||
);
|
);
|
||||||
app.use(
|
|
||||||
["/locales", "/locales/*"],
|
|
||||||
express.static(path.join(__dirname, "..", "frontend", "build", "locales"))
|
|
||||||
);
|
|
||||||
app.get(["/app/network/*"], function (req, res) {
|
app.get(["/app/network/*"], function (req, res) {
|
||||||
res.sendFile(path.join(__dirname, "..", "frontend", "build", "index.html"));
|
res.sendFile(path.join(__dirname, "..", "frontend", "build", "index.html"));
|
||||||
});
|
});
|
||||||
|
@ -64,21 +49,9 @@ if (
|
||||||
}
|
}
|
||||||
|
|
||||||
initAdmin().then(function (admin) {
|
initAdmin().then(function (admin) {
|
||||||
db.defaults({ users: [admin], networks: [] }).write();
|
db.defaults({ users: [admin], networks: {} }).write();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.ZU_LAST_SEEN_FETCH !== "false") {
|
|
||||||
let schedule = process.env.ZU_LAST_SEEN_SCHEDULE || "*/5 * * * *";
|
|
||||||
Cron(schedule, () => {
|
|
||||||
console.debug("Running scheduled job");
|
|
||||||
const networks = db.get("networks").value();
|
|
||||||
networks.forEach(async (network) => {
|
|
||||||
console.debug("Processing network " + network.id);
|
|
||||||
await pingAll(network);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const routerAPI = express.Router();
|
const routerAPI = express.Router();
|
||||||
const routerController = express.Router();
|
const routerController = express.Router();
|
||||||
|
|
||||||
|
@ -94,9 +67,9 @@ app.use("/controller", routerController); // other controller-specific routes
|
||||||
app.get("*", async function (req, res) {
|
app.get("*", async function (req, res) {
|
||||||
res.status(404).json({ error: "404 Not found" });
|
res.status(404).json({ error: "404 Not found" });
|
||||||
});
|
});
|
||||||
app.use(function (err, req, res, next) {
|
app.use(async function (err, req, res) {
|
||||||
console.error(err.stack);
|
console.error(err.stack); // TODO: replace with production logger
|
||||||
res.status(500).json({ error: "500 Internal server error" });
|
res.status(500).json({ error: "500 Internal server error" });
|
||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
module.exports = app;
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import dotenv from "dotenv";
|
require("dotenv").config();
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import app from "../app.js";
|
var app = require("../app");
|
||||||
|
var debug = require("debug")("zero-ui:server");
|
||||||
console.log("zero-ui:server");
|
var http = require("http");
|
||||||
import http from "node:http";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get port from environment and store in Express.
|
* Get port from environment and store in Express.
|
||||||
|
@ -28,7 +26,7 @@ var server = http.createServer(app);
|
||||||
* Listen on provided port, on all network interfaces.
|
* Listen on provided port, on all network interfaces.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
server.listen(port, process.env.LISTEN_ADDRESS || "0.0.0.0");
|
server.listen(port);
|
||||||
server.on("error", onError);
|
server.on("error", onError);
|
||||||
server.on("listening", onListening);
|
server.on("listening", onListening);
|
||||||
|
|
||||||
|
@ -84,6 +82,6 @@ function onError(error) {
|
||||||
|
|
||||||
function onListening() {
|
function onListening() {
|
||||||
var addr = server.address();
|
var addr = server.address();
|
||||||
var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr?.port;
|
var bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port;
|
||||||
console.log("Listening on " + bind);
|
debug("Listening on " + bind);
|
||||||
}
|
}
|
1
backend/global.d.ts
vendored
1
backend/global.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
declare module "axios";
|
|
6
backend/jsconfig.json
Normal file
6
backend/jsconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"exclude": ["node_modules", "**/node_modules/*"],
|
||||||
|
"typeAcquisition": {
|
||||||
|
"exclude": ["dotenv"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +1,21 @@
|
||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "zero-ui-backend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./bin/www",
|
"start": "node ./bin/www"
|
||||||
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
|
|
||||||
"typecheck": "tsc --pretty --noEmit -p tsconfig.json"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.21.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"croner": "^7.0.2",
|
"debug": "~4.3.1",
|
||||||
"debug": "^4.3.4",
|
"dotenv": "^8.2.0",
|
||||||
"dotenv": "^16.3.1",
|
"express": "~4.17.1",
|
||||||
"express": "^4.18.2",
|
|
||||||
"express-bearer-token": "^2.4.0",
|
"express-bearer-token": "^2.4.0",
|
||||||
"express-rate-limit": "^7.1.1",
|
"helmet": "^4.4.1",
|
||||||
"helmet": "^5.1.1",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lowdb": "^1.0.0",
|
"lowdb": "^1.0.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "~1.10.0",
|
||||||
"pbkdf2-wrapper": "^1.3.4"
|
"p-debounce": "^3.0.1",
|
||||||
},
|
"pbkdf2-wrapper": "^1.3.2"
|
||||||
"devDependencies": {
|
|
||||||
"@types/compression": "^1.7.3",
|
|
||||||
"@types/debug": "^4.1.9",
|
|
||||||
"@types/express": "^4.17.18",
|
|
||||||
"@types/lodash": "^4.14.199",
|
|
||||||
"@types/morgan": "^1.9.6",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
|
||||||
"@typescript-eslint/parser": "^6.7.4",
|
|
||||||
"eslint": "^8.51.0",
|
|
||||||
"eslint-plugin-import": "^2.28.1",
|
|
||||||
"eslint-plugin-jsdoc": "^46.8.2",
|
|
||||||
"eslint-plugin-n": "^16.1.0",
|
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
|
||||||
"eslint-plugin-security": "^1.7.1",
|
|
||||||
"eslint-plugin-sonarjs": "^0.21.0",
|
|
||||||
"eslint-plugin-unicorn": "^48.0.1",
|
|
||||||
"typescript": "^5.2.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,9 @@
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
import rateLimit from "express-rate-limit";
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
import * as auth from "../services/auth.js";
|
const auth = require("../services/auth");
|
||||||
|
|
||||||
const loginLimiter = rateLimit({
|
router.post("/login", async function (req, res) {
|
||||||
windowMs: (Number(process.env.ZU_LOGIN_LIMIT_WINDOW) || 30) * 60 * 1000, // 30 minutes
|
|
||||||
max: Number(process.env.ZU_LOGIN_LIMIT_ATTEMPTS) || 50, // limit each IP to 50 requests per windowMs
|
|
||||||
message: {
|
|
||||||
status: 429,
|
|
||||||
error: "tooManyAttempts",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const loginLimiterWrapper = (req, res, next) => {
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV === "production" &&
|
|
||||||
process.env.ZU_LOGIN_LIMIT === "true"
|
|
||||||
) {
|
|
||||||
return loginLimiter(req, res, next);
|
|
||||||
} else {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
router.get("/login", async function (req, res) {
|
|
||||||
if (process.env.ZU_DISABLE_AUTH === "true") {
|
|
||||||
res.send({ enabled: false });
|
|
||||||
} else {
|
|
||||||
res.send({ enabled: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post("/login", loginLimiterWrapper, async function (req, res) {
|
|
||||||
if (req.body.username && req.body.password) {
|
if (req.body.username && req.body.password) {
|
||||||
auth.authorize(req.body.username, req.body.password, function (err, user) {
|
auth.authorize(req.body.username, req.body.password, function (err, user) {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -49,4 +19,4 @@ router.post("/login", loginLimiterWrapper, async function (req, res) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
import * as auth from "../services/auth.js";
|
const auth = require("../services/auth");
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
|
|
||||||
router.get("/status", auth.isAuthorized, async function (req, res) {
|
router.get("/status", auth.isAuthorized, async function (req, res) {
|
||||||
api.get("status").then(function (controllerRes) {
|
api.get("status").then(function (controllerRes) {
|
||||||
|
@ -10,4 +10,4 @@ router.get("/status", auth.isAuthorized, async function (req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
const router = express.Router({ mergeParams: true });
|
const router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
import * as auth from "../services/auth.js";
|
const auth = require("../services/auth");
|
||||||
import * as member from "../services/member.js";
|
const member = require("../services/member");
|
||||||
|
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
|
|
||||||
// get all members
|
// get all members
|
||||||
router.get("/", auth.isAuthorized, async function (req, res) {
|
router.get("/", auth.isAuthorized, async function (req, res) {
|
||||||
// @ts-ignore
|
|
||||||
const nwid = req.params.nwid;
|
const nwid = req.params.nwid;
|
||||||
api
|
api
|
||||||
.get("controller/network/" + nwid + "/member")
|
.get("controller/network/" + nwid + "/member")
|
||||||
|
@ -17,14 +16,13 @@ router.get("/", auth.isAuthorized, async function (req, res) {
|
||||||
const data = await member.getMembersData(nwid, mids);
|
const data = await member.getMembersData(nwid, mids);
|
||||||
res.send(data);
|
res.send(data);
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function () {
|
||||||
res.status(404).send({ error: `Network not found ${err}` });
|
res.status(404).send({ error: "Network not found" });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// get member
|
// get member
|
||||||
router.get("/:mid", auth.isAuthorized, async function (req, res) {
|
router.get("/:mid", auth.isAuthorized, async function (req, res) {
|
||||||
// @ts-ignore
|
|
||||||
const nwid = req.params.nwid;
|
const nwid = req.params.nwid;
|
||||||
const mid = req.params.mid;
|
const mid = req.params.mid;
|
||||||
const data = await member.getMembersData(nwid, [mid]);
|
const data = await member.getMembersData(nwid, [mid]);
|
||||||
|
@ -37,7 +35,6 @@ router.get("/:mid", auth.isAuthorized, async function (req, res) {
|
||||||
|
|
||||||
// update member
|
// update member
|
||||||
router.post("/:mid", auth.isAuthorized, async function (req, res) {
|
router.post("/:mid", auth.isAuthorized, async function (req, res) {
|
||||||
// @ts-ignore
|
|
||||||
const nwid = req.params.nwid;
|
const nwid = req.params.nwid;
|
||||||
const mid = req.params.mid;
|
const mid = req.params.mid;
|
||||||
member.updateMemberAdditionalData(nwid, mid, req.body);
|
member.updateMemberAdditionalData(nwid, mid, req.body);
|
||||||
|
@ -59,7 +56,6 @@ router.post("/:mid", auth.isAuthorized, async function (req, res) {
|
||||||
|
|
||||||
// delete member
|
// delete member
|
||||||
router.delete("/:mid", auth.isAuthorized, async function (req, res) {
|
router.delete("/:mid", auth.isAuthorized, async function (req, res) {
|
||||||
// @ts-ignore
|
|
||||||
const nwid = req.params.nwid;
|
const nwid = req.params.nwid;
|
||||||
const mid = req.params.mid;
|
const mid = req.params.mid;
|
||||||
member.deleteMemberAdditionalData(nwid, mid);
|
member.deleteMemberAdditionalData(nwid, mid);
|
||||||
|
@ -86,4 +82,4 @@ router.delete("/:mid", auth.isAuthorized, async function (req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
import * as auth from "../services/auth.js";
|
const auth = require("../services/auth");
|
||||||
import * as network from "../services/network.js";
|
const network = require("../services/network");
|
||||||
|
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
import { defaultRules } from "../utils/constants.js";
|
const constants = require("../utils/constants");
|
||||||
import { getZTAddress } from "../utils/zt-address.js";
|
const getZTAddress = require("../utils/zt-address");
|
||||||
|
|
||||||
let ZT_ADDRESS = null;
|
let ZT_ADDRESS = null;
|
||||||
getZTAddress().then(function (address) {
|
getZTAddress().then(function (address) {
|
||||||
|
@ -22,6 +22,26 @@ router.get("/", auth.isAuthorized, async function (req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// create new network
|
||||||
|
router.post("/", auth.isAuthorized, async function (req, res) {
|
||||||
|
let reqData = req.body;
|
||||||
|
if (reqData.config) {
|
||||||
|
const config = reqData.config;
|
||||||
|
delete reqData.config;
|
||||||
|
reqData = config;
|
||||||
|
reqData.rules = JSON.parse(constants.defaultRules);
|
||||||
|
} else {
|
||||||
|
res.status(400).send({ error: "Bad request" });
|
||||||
|
}
|
||||||
|
api
|
||||||
|
.post("controller/network/" + ZT_ADDRESS + "______", reqData)
|
||||||
|
.then(async function (controllerRes) {
|
||||||
|
await network.createNetworkAdditionalData(controllerRes.data);
|
||||||
|
const data = await network.getNetworksData([controllerRes.data.id]);
|
||||||
|
res.send(data[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// get network
|
// get network
|
||||||
router.get("/:nwid", auth.isAuthorized, async function (req, res) {
|
router.get("/:nwid", auth.isAuthorized, async function (req, res) {
|
||||||
const nwid = req.params.nwid;
|
const nwid = req.params.nwid;
|
||||||
|
@ -33,26 +53,6 @@ router.get("/:nwid", auth.isAuthorized, async function (req, res) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// create new network
|
|
||||||
router.post("/", auth.isAuthorized, async function (req, res) {
|
|
||||||
let reqData = req.body;
|
|
||||||
if (reqData.config) {
|
|
||||||
const config = reqData.config;
|
|
||||||
delete reqData.config;
|
|
||||||
reqData = config;
|
|
||||||
reqData.rules = JSON.parse(defaultRules);
|
|
||||||
} else {
|
|
||||||
res.status(400).send({ error: "Bad request" });
|
|
||||||
}
|
|
||||||
api
|
|
||||||
.post("controller/network/" + ZT_ADDRESS + "______", reqData)
|
|
||||||
.then(async function (controllerRes) {
|
|
||||||
await network.createNetworkAdditionalData(controllerRes.data.id);
|
|
||||||
const data = await network.getNetworksData([controllerRes.data.id]);
|
|
||||||
res.send(data[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// update network
|
// update network
|
||||||
router.post("/:nwid", auth.isAuthorized, async function (req, res) {
|
router.post("/:nwid", auth.isAuthorized, async function (req, res) {
|
||||||
const nwid = req.params.nwid;
|
const nwid = req.params.nwid;
|
||||||
|
@ -87,4 +87,4 @@ router.delete("/:nwid", auth.isAuthorized, async function (req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,35 +1,33 @@
|
||||||
import { db } from "../utils/db.js";
|
const db = require("../utils/db");
|
||||||
import verifyHash from "pbkdf2-wrapper/verifyHash.js";
|
const verifyHash = require("pbkdf2-wrapper/verifyHash");
|
||||||
|
|
||||||
export async function authorize(username, password, callback) {
|
exports.authorize = authorize;
|
||||||
|
async function authorize(username, password, callback) {
|
||||||
try {
|
try {
|
||||||
var users = await db.get("users");
|
var users = await db.get("users");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
const user = users.find({ username: username });
|
const user = users.find({ username: username });
|
||||||
if (!user.value()) return callback(new Error("logInFailed")); // If return "user not found" someone can do a user listing
|
if (!user.value()) return callback(new Error("Cannot find user"));
|
||||||
const verified = await verifyHash(password, user.value()["password_hash"]);
|
const verified = await verifyHash(password, user.value()["password_hash"]);
|
||||||
if (verified) {
|
if (verified) {
|
||||||
return callback(null, user.value());
|
return callback(null, user.value());
|
||||||
} else {
|
} else {
|
||||||
return callback(new Error("logInFailed"));
|
return callback(new Error("Invalid password"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isAuthorized(req, res, next) {
|
exports.isAuthorized = isAuthorized;
|
||||||
if (process.env.ZU_DISABLE_AUTH === "true") {
|
async function isAuthorized(req, res, next) {
|
||||||
next();
|
if (req.token) {
|
||||||
} else {
|
const user = await db.get("users").find({ token: req.token }).value();
|
||||||
if (req.token) {
|
if (user) {
|
||||||
const user = await db.get("users").find({ token: req.token }).value();
|
next();
|
||||||
if (user) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
res.status(403).send({ error: "Invalid token" });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
res.status(401).send({ error: "Specify token" });
|
res.status(403).send({ error: "Invalid token" });
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
res.status(401).send({ error: "Specify token" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import _ from "lodash";
|
const _ = require("lodash");
|
||||||
import axios from "axios";
|
const axios = require("axios");
|
||||||
|
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
import { db } from "../utils/db.js";
|
const db = require("../utils/db");
|
||||||
import { getZTAddress } from "../utils/zt-address.js";
|
const getZTAddress = require("../utils/zt-address");
|
||||||
|
|
||||||
let ZT_ADDRESS = null;
|
let ZT_ADDRESS = null;
|
||||||
getZTAddress().then(function (address) {
|
getZTAddress().then(function (address) {
|
||||||
|
@ -20,43 +20,24 @@ async function getPeer(mid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMemberAdditionalData(data) {
|
async function getMemberAdditionalData(data) {
|
||||||
// DB BUG INITIALIZATION MIGRATION
|
const additionalData = db
|
||||||
const network = db.get("networks").find({ id: data.nwid });
|
|
||||||
network.defaults({ members: [] }).get("members").write();
|
|
||||||
// END MIGRATION SECTION
|
|
||||||
|
|
||||||
const member = db
|
|
||||||
.get("networks")
|
.get("networks")
|
||||||
.find({ id: data.nwid })
|
.find({ id: data.nwid })
|
||||||
.get("members")
|
.get("members")
|
||||||
.find({ id: data.id });
|
.find({ id: data.id })
|
||||||
|
.get("additionalConfig")
|
||||||
const additionalData = member.get("additionalConfig").value() || {};
|
.value();
|
||||||
const lastOnline = member.get("lastOnline").value() || 0;
|
|
||||||
|
|
||||||
const peer = await getPeer(data.id);
|
const peer = await getPeer(data.id);
|
||||||
let peerData = {};
|
let peerData = {};
|
||||||
if (peer && !_.isEmpty(peer)) {
|
if (peer) {
|
||||||
peerData.latency = peer.latency;
|
peerData.latency = peer.latency;
|
||||||
if (peer.latency !== -1) peerData.online = 1;
|
peerData.online = peer.latency !== -1;
|
||||||
if (peer.latency == -1) peerData.online = 2;
|
|
||||||
peerData.clientVersion = peer.version;
|
peerData.clientVersion = peer.version;
|
||||||
if (peer.paths.length > 0) {
|
if (peer.paths[0]) {
|
||||||
let path = peer.paths.filter((p) => {
|
peerData.lastOnline = peer.paths[0].lastReceive;
|
||||||
let ret = p.active && !p.expired;
|
peerData.physicalAddress = peer.paths[0].address.split("/")[0];
|
||||||
if (typeof p.preferred !== "undefined") {
|
|
||||||
ret = ret && p.preferred;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
if (path.length > 0) {
|
|
||||||
peerData.lastOnline = path[0].lastReceive;
|
|
||||||
peerData.physicalAddress = path[0].address.split("/")[0];
|
|
||||||
peerData.physicalPort = path[0].address.split("/")[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
peerData.online = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete data.lastAuthorizedCredential;
|
delete data.lastAuthorizedCredential;
|
||||||
|
@ -67,12 +48,11 @@ async function getMemberAdditionalData(data) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: data.nwid + "-" + data.id,
|
id: data.nwid + "-" + data.id,
|
||||||
clock: new Date().getTime(),
|
type: "Member",
|
||||||
|
clock: Math.floor(new Date().getTime() / 1000),
|
||||||
networkId: data.nwid,
|
networkId: data.nwid,
|
||||||
nodeId: data.id,
|
nodeId: data.id,
|
||||||
controllerId: ZT_ADDRESS,
|
controllerId: ZT_ADDRESS,
|
||||||
// @ts-ignore
|
|
||||||
lastOnline: lastOnline,
|
|
||||||
...additionalData,
|
...additionalData,
|
||||||
...peerData,
|
...peerData,
|
||||||
config: data,
|
config: data,
|
||||||
|
@ -86,12 +66,12 @@ async function filterDeleted(nwid, mid) {
|
||||||
.get("members")
|
.get("members")
|
||||||
.find({ id: mid });
|
.find({ id: mid });
|
||||||
|
|
||||||
let deleted = member.get("deleted").value() || false;
|
if (!member.get("deleted").value()) return mid;
|
||||||
if (!deleted) return mid;
|
|
||||||
else return;
|
else return;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMembersData(nwid, mids) {
|
exports.getMembersData = getMembersData;
|
||||||
|
async function getMembersData(nwid, mids) {
|
||||||
const prefix = "/controller/network/" + nwid + "/member/";
|
const prefix = "/controller/network/" + nwid + "/member/";
|
||||||
const filtered = (
|
const filtered = (
|
||||||
await Promise.all(mids.map(async (mid) => await filterDeleted(nwid, mid)))
|
await Promise.all(mids.map(async (mid) => await filterDeleted(nwid, mid)))
|
||||||
|
@ -105,7 +85,7 @@ export async function getMembersData(nwid, mids) {
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.catch(function (err) {
|
.catch(function () {
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,7 +98,8 @@ export async function getMembersData(nwid, mids) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateMemberAdditionalData(nwid, mid, data) {
|
exports.updateMemberAdditionalData = updateMemberAdditionalData;
|
||||||
|
async function updateMemberAdditionalData(nwid, mid, data) {
|
||||||
if (data.config && data.config.authorized) {
|
if (data.config && data.config.authorized) {
|
||||||
db.get("networks")
|
db.get("networks")
|
||||||
.filter({ id: nwid })
|
.filter({ id: nwid })
|
||||||
|
@ -170,7 +151,8 @@ export async function updateMemberAdditionalData(nwid, mid, data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteMemberAdditionalData(nwid, mid) {
|
exports.deleteMemberAdditionalData = deleteMemberAdditionalData;
|
||||||
|
async function deleteMemberAdditionalData(nwid, mid) {
|
||||||
// ZT controller bug
|
// ZT controller bug
|
||||||
/* db.get("networks")
|
/* db.get("networks")
|
||||||
.find({ id: nwid })
|
.find({ id: nwid })
|
||||||
|
@ -179,8 +161,6 @@ export async function deleteMemberAdditionalData(nwid, mid) {
|
||||||
.write();
|
.write();
|
||||||
*/
|
*/
|
||||||
|
|
||||||
await updateMemberAdditionalData(nwid, mid, {});
|
|
||||||
|
|
||||||
db.get("networks")
|
db.get("networks")
|
||||||
.filter({ id: nwid })
|
.filter({ id: nwid })
|
||||||
.map("members")
|
.map("members")
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import _ from "lodash";
|
const _ = require("lodash");
|
||||||
import axios from "axios";
|
const axios = require("axios");
|
||||||
|
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
import { db } from "../utils/db.js";
|
const db = require("../utils/db");
|
||||||
import { defaultRulesSource } from "../utils/constants.js";
|
const constants = require("../utils/constants");
|
||||||
|
|
||||||
export async function getNetworkAdditionalData(data) {
|
async function getNetworkAdditionalData(data) {
|
||||||
let additionalData = db
|
let additionalData = db
|
||||||
.get("networks")
|
.get("networks")
|
||||||
.find({ id: data.id })
|
.find({ id: data.id })
|
||||||
.get("additionalConfig");
|
.get("additionalConfig");
|
||||||
|
|
||||||
if (!additionalData.value()) {
|
if (!additionalData.value()) {
|
||||||
createNetworkAdditionalData(data.id);
|
createNetworkAdditionalData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete data.rulesSource;
|
delete data.rulesSource;
|
||||||
|
@ -23,13 +23,15 @@ export async function getNetworkAdditionalData(data) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
clock: new Date().getTime(),
|
type: "Network",
|
||||||
|
clock: Math.floor(new Date().getTime() / 1000),
|
||||||
...additionalData.value(),
|
...additionalData.value(),
|
||||||
config: data,
|
config: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getNetworksData(nwids) {
|
exports.getNetworksData = getNetworksData;
|
||||||
|
async function getNetworksData(nwids) {
|
||||||
const prefix = "/controller/network/";
|
const prefix = "/controller/network/";
|
||||||
const links = nwids.map((nwid) => prefix + nwid);
|
const links = nwids.map((nwid) => prefix + nwid);
|
||||||
|
|
||||||
|
@ -53,22 +55,21 @@ export async function getNetworksData(nwids) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNetworkAdditionalData(nwid) {
|
exports.createNetworkAdditionalData = createNetworkAdditionalData;
|
||||||
|
async function createNetworkAdditionalData(data) {
|
||||||
const saveData = {
|
const saveData = {
|
||||||
id: nwid,
|
id: data.id,
|
||||||
additionalConfig: {
|
additionalConfig: {
|
||||||
description: "",
|
description: "",
|
||||||
rulesSource: defaultRulesSource,
|
rulesSource: constants.defaultRulesSource,
|
||||||
tagsByName: {},
|
|
||||||
capabilitiesByName: {},
|
|
||||||
},
|
},
|
||||||
members: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
db.get("networks").push(saveData).write();
|
db.get("networks").push(saveData).write();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateNetworkAdditionalData(nwid, data) {
|
exports.updateNetworkAdditionalData = updateNetworkAdditionalData;
|
||||||
|
async function updateNetworkAdditionalData(nwid, data) {
|
||||||
let additionalData = {};
|
let additionalData = {};
|
||||||
|
|
||||||
if (data.hasOwnProperty("description")) {
|
if (data.hasOwnProperty("description")) {
|
||||||
|
@ -77,12 +78,6 @@ export async function updateNetworkAdditionalData(nwid, data) {
|
||||||
if (data.hasOwnProperty("rulesSource")) {
|
if (data.hasOwnProperty("rulesSource")) {
|
||||||
additionalData.rulesSource = data.rulesSource;
|
additionalData.rulesSource = data.rulesSource;
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty("tagsByName")) {
|
|
||||||
additionalData.tagsByName = data.tagsByName;
|
|
||||||
}
|
|
||||||
if (data.hasOwnProperty("capabilitiesByName")) {
|
|
||||||
additionalData.capabilitiesByName = data.capabilitiesByName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (additionalData) {
|
if (additionalData) {
|
||||||
db.get("networks")
|
db.get("networks")
|
||||||
|
@ -93,6 +88,7 @@ export async function updateNetworkAdditionalData(nwid, data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteNetworkAdditionalData(nwid) {
|
exports.deleteNetworkAdditionalData = deleteNetworkAdditionalData;
|
||||||
|
async function deleteNetworkAdditionalData(nwid) {
|
||||||
db.get("networks").remove({ id: nwid }).write();
|
db.get("networks").remove({ id: nwid }).write();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"rootDir": ".",
|
|
||||||
"baseUrl": ".",
|
|
||||||
"module": "NodeNext",
|
|
||||||
"moduleResolution": "NodeNext"
|
|
||||||
},
|
|
||||||
"include": ["."]
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const defaultRulesSource = `
|
exports.defaultRulesSource = `
|
||||||
# This is a default rule set that allows IPv4 and IPv6 traffic but otherwise
|
# This is a default rule set that allows IPv4 and IPv6 traffic but otherwise
|
||||||
# behaves like a standard Ethernet switch.
|
# behaves like a standard Ethernet switch.
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ drop
|
||||||
accept;
|
accept;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const defaultRules = `
|
exports.defaultRules = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"type": "MATCH_ETHERTYPE",
|
"type": "MATCH_ETHERTYPE",
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
import axios from "axios";
|
const axios = require("axios");
|
||||||
import fs from "node:fs";
|
const fs = require("fs");
|
||||||
import os from "node:os";
|
|
||||||
|
|
||||||
const baseURL = process.env.ZU_CONTROLLER_ENDPOINT || "http://localhost:9993/";
|
const baseURL = process.env.ZU_CONTROLLER_ENDPOINT || "http://localhost:9993/";
|
||||||
|
|
||||||
var token;
|
var token;
|
||||||
if (process.env.ZU_CONTROLLER_TOKEN) {
|
if (process.env.ZU_CONTROLLER_TOKEN) {
|
||||||
token = process.env.ZU_CONTROLLER_TOKEN;
|
token = process.env.ZU_CONTROLLER_TOKEN;
|
||||||
} else if (os.platform() === "linux") {
|
|
||||||
token = fs.readFileSync("/var/lib/zerotier-one/authtoken.secret", "utf8");
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Please provide ZU_CONTROLLER_TOKEN in environment");
|
token = fs.readFileSync("/var/lib/zerotier-one/authtoken.secret", "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = axios.create({
|
module.exports = axios.create({
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
responseType: "json",
|
responseType: "json",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import low from "lowdb";
|
const low = require("lowdb");
|
||||||
import FileSync from "lowdb/adapters/FileSync.js";
|
const FileSync = require("lowdb/adapters/FileSync");
|
||||||
|
|
||||||
const adapter = new FileSync(process.env.ZU_DATAPATH || "data/db.json");
|
const adapter = new FileSync(process.env.ZU_DATAPATH || "data/db.json");
|
||||||
|
|
||||||
export const db = low(adapter);
|
const db = low(adapter);
|
||||||
|
|
||||||
|
module.exports = db;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import crypto from "crypto";
|
const crypto = require("crypto");
|
||||||
import hashPassword from "pbkdf2-wrapper/hashText.js";
|
const hashPassword = require("pbkdf2-wrapper/hashText");
|
||||||
|
|
||||||
export async function initAdmin() {
|
module.exports = async function () {
|
||||||
if (!process.env.ZU_DEFAULT_PASSWORD || !process.env.ZU_DEFAULT_USERNAME) {
|
if (!process.env.ZU_DEFAULT_PASSWORD || !process.env.ZU_DEFAULT_USERNAME) {
|
||||||
console.error("ZU_DEFAULT_PASSWORD or ZU_DEFAULT_USERNAME not found!");
|
console.error("ZU_DEFAULT_PASSWORD or ZU_DEFAULT_USERNAME not found!");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -13,4 +13,4 @@ export async function initAdmin() {
|
||||||
password_hash: hash,
|
password_hash: hash,
|
||||||
token: crypto.randomBytes(16).toString("hex"),
|
token: crypto.randomBytes(16).toString("hex"),
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
import { api } from "./controller-api.js";
|
|
||||||
import { db } from "./db.js";
|
|
||||||
|
|
||||||
export async function pingAll(network) {
|
|
||||||
await Promise.all(
|
|
||||||
network.members.map(async (member) => {
|
|
||||||
console.debug("Processing member " + member.id);
|
|
||||||
api
|
|
||||||
.get("peer/" + member.id)
|
|
||||||
.then(function (controllerResp) {
|
|
||||||
if (!_.isEmpty(controllerResp.data)) {
|
|
||||||
// write lastOnline field in db
|
|
||||||
db.get("networks")
|
|
||||||
.filter({ id: network.id })
|
|
||||||
.map("members")
|
|
||||||
.first()
|
|
||||||
.filter({ id: member.id })
|
|
||||||
.first()
|
|
||||||
.set("lastOnline", new Date().getTime())
|
|
||||||
.write();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
console.debug("Couldn't fetch", member.id);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,13 +1,6 @@
|
||||||
import { api } from "../utils/controller-api.js";
|
const api = require("../utils/controller-api");
|
||||||
|
|
||||||
export async function getZTAddress() {
|
module.exports = async function () {
|
||||||
try {
|
const res = await api.get("status");
|
||||||
const res = await api.get("status");
|
return res.data.address;
|
||||||
return res.data.address;
|
};
|
||||||
} catch (err) {
|
|
||||||
console.error(
|
|
||||||
// @ts-ignore
|
|
||||||
"Couldn't connect to the controller on " + err.config.baseURL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
554
backend/yarn.lock
Normal file
554
backend/yarn.lock
Normal file
|
@ -0,0 +1,554 @@
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
abbott@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/abbott/-/abbott-1.1.3.tgz"
|
||||||
|
integrity sha1-JvOtm7vb/+LFa1sDdU5ZgasOXlw=
|
||||||
|
|
||||||
|
accepts@~1.3.5, accepts@~1.3.7:
|
||||||
|
version "1.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz"
|
||||||
|
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
|
||||||
|
dependencies:
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
negotiator "0.6.2"
|
||||||
|
|
||||||
|
array-flatten@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz"
|
||||||
|
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||||
|
|
||||||
|
axios@^0.21.1:
|
||||||
|
version "0.21.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz"
|
||||||
|
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.10.0"
|
||||||
|
|
||||||
|
basic-auth@~2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz"
|
||||||
|
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
|
||||||
|
body-parser@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz"
|
||||||
|
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
http-errors "1.7.2"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
qs "6.7.0"
|
||||||
|
raw-body "2.4.0"
|
||||||
|
type-is "~1.6.17"
|
||||||
|
|
||||||
|
bytes@3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||||
|
integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
|
||||||
|
|
||||||
|
bytes@3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz"
|
||||||
|
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||||
|
|
||||||
|
compressible@~2.0.16:
|
||||||
|
version "2.0.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
|
||||||
|
integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
|
||||||
|
dependencies:
|
||||||
|
mime-db ">= 1.43.0 < 2"
|
||||||
|
|
||||||
|
compression@^1.7.4:
|
||||||
|
version "1.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
|
||||||
|
integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
|
||||||
|
dependencies:
|
||||||
|
accepts "~1.3.5"
|
||||||
|
bytes "3.0.0"
|
||||||
|
compressible "~2.0.16"
|
||||||
|
debug "2.6.9"
|
||||||
|
on-headers "~1.0.2"
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
content-disposition@0.5.3:
|
||||||
|
version "0.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz"
|
||||||
|
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
|
||||||
|
content-type@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz"
|
||||||
|
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||||
|
|
||||||
|
cookie-parser@^1.4.4:
|
||||||
|
version "1.4.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.5.tgz"
|
||||||
|
integrity sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==
|
||||||
|
dependencies:
|
||||||
|
cookie "0.4.0"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz"
|
||||||
|
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||||
|
|
||||||
|
cookie@0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz"
|
||||||
|
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||||
|
|
||||||
|
cookie@^0.3.1:
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz"
|
||||||
|
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz"
|
||||||
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
|
debug@~4.3.1:
|
||||||
|
version "4.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz"
|
||||||
|
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
|
depd@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz"
|
||||||
|
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||||
|
|
||||||
|
depd@~2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz"
|
||||||
|
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||||
|
|
||||||
|
destroy@~1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz"
|
||||||
|
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
|
||||||
|
|
||||||
|
dotenv@^8.2.0:
|
||||||
|
version "8.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz"
|
||||||
|
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||||
|
|
||||||
|
ee-first@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz"
|
||||||
|
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||||
|
|
||||||
|
encodeurl@~1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz"
|
||||||
|
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||||
|
|
||||||
|
escape-html@~1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz"
|
||||||
|
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||||
|
|
||||||
|
etag@~1.8.1:
|
||||||
|
version "1.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz"
|
||||||
|
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||||
|
|
||||||
|
express-bearer-token@^2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/express-bearer-token/-/express-bearer-token-2.4.0.tgz"
|
||||||
|
integrity sha512-2+kRZT2xo+pmmvSY7Ma5FzxTJpO3kGaPCEXPbAm3GaoZ/z6FE4K6L7cvs1AUZwY2xkk15PcQw7t4dWjsl5rdJw==
|
||||||
|
dependencies:
|
||||||
|
cookie "^0.3.1"
|
||||||
|
cookie-parser "^1.4.4"
|
||||||
|
|
||||||
|
express@~4.17.1:
|
||||||
|
version "4.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz"
|
||||||
|
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
|
||||||
|
dependencies:
|
||||||
|
accepts "~1.3.7"
|
||||||
|
array-flatten "1.1.1"
|
||||||
|
body-parser "1.19.0"
|
||||||
|
content-disposition "0.5.3"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
cookie "0.4.0"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
finalhandler "~1.1.2"
|
||||||
|
fresh "0.5.2"
|
||||||
|
merge-descriptors "1.0.1"
|
||||||
|
methods "~1.1.2"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
path-to-regexp "0.1.7"
|
||||||
|
proxy-addr "~2.0.5"
|
||||||
|
qs "6.7.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
safe-buffer "5.1.2"
|
||||||
|
send "0.17.1"
|
||||||
|
serve-static "1.14.1"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
type-is "~1.6.18"
|
||||||
|
utils-merge "1.0.1"
|
||||||
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
finalhandler@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz"
|
||||||
|
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
|
follow-redirects@^1.10.0:
|
||||||
|
version "1.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz"
|
||||||
|
integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==
|
||||||
|
|
||||||
|
forwarded@~0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz"
|
||||||
|
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
|
||||||
|
|
||||||
|
fresh@0.5.2:
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz"
|
||||||
|
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||||
|
|
||||||
|
graceful-fs@^4.1.3:
|
||||||
|
version "4.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz"
|
||||||
|
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
|
||||||
|
|
||||||
|
helmet@^4.4.1:
|
||||||
|
version "4.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.4.1.tgz#a17e1444d81d7a83ddc6e6f9bc6e2055b994efe7"
|
||||||
|
integrity sha512-G8tp0wUMI7i8wkMk2xLcEvESg5PiCitFMYgGRc/PwULB0RVhTP5GFdxOwvJwp9XVha8CuS8mnhmE8I/8dx/pbw==
|
||||||
|
|
||||||
|
http-errors@1.7.2:
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz"
|
||||||
|
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||||
|
dependencies:
|
||||||
|
depd "~1.1.2"
|
||||||
|
inherits "2.0.3"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses ">= 1.5.0 < 2"
|
||||||
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
|
http-errors@~1.7.2:
|
||||||
|
version "1.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz"
|
||||||
|
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||||
|
dependencies:
|
||||||
|
depd "~1.1.2"
|
||||||
|
inherits "2.0.4"
|
||||||
|
setprototypeof "1.1.1"
|
||||||
|
statuses ">= 1.5.0 < 2"
|
||||||
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
|
iconv-lite@0.4.24:
|
||||||
|
version "0.4.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz"
|
||||||
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
|
inherits@2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz"
|
||||||
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
|
inherits@2.0.4:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz"
|
||||||
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
|
ipaddr.js@1.9.1:
|
||||||
|
version "1.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
|
||||||
|
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||||
|
|
||||||
|
is-promise@^2.1.0:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz"
|
||||||
|
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
|
||||||
|
|
||||||
|
lodash@4:
|
||||||
|
version "4.17.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz"
|
||||||
|
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||||
|
|
||||||
|
lodash@^4.17.21:
|
||||||
|
version "4.17.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
|
lowdb@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz"
|
||||||
|
integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.1.3"
|
||||||
|
is-promise "^2.1.0"
|
||||||
|
lodash "4"
|
||||||
|
pify "^3.0.0"
|
||||||
|
steno "^0.4.1"
|
||||||
|
|
||||||
|
media-typer@0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz"
|
||||||
|
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||||
|
|
||||||
|
merge-descriptors@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz"
|
||||||
|
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||||
|
|
||||||
|
methods@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz"
|
||||||
|
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||||
|
|
||||||
|
mime-db@1.45.0:
|
||||||
|
version "1.45.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz"
|
||||||
|
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
|
||||||
|
|
||||||
|
"mime-db@>= 1.43.0 < 2":
|
||||||
|
version "1.46.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee"
|
||||||
|
integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==
|
||||||
|
|
||||||
|
mime-types@~2.1.24:
|
||||||
|
version "2.1.28"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz"
|
||||||
|
integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.45.0"
|
||||||
|
|
||||||
|
mime@1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz"
|
||||||
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
|
morgan@~1.10.0:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz"
|
||||||
|
integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
|
||||||
|
dependencies:
|
||||||
|
basic-auth "~2.0.1"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~2.0.0"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
on-headers "~1.0.2"
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz"
|
||||||
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
|
ms@2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz"
|
||||||
|
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||||
|
|
||||||
|
ms@2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz"
|
||||||
|
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||||
|
|
||||||
|
negotiator@0.6.2:
|
||||||
|
version "0.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz"
|
||||||
|
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||||
|
|
||||||
|
on-finished@~2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz"
|
||||||
|
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||||
|
dependencies:
|
||||||
|
ee-first "1.1.1"
|
||||||
|
|
||||||
|
on-headers@~1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz"
|
||||||
|
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
|
||||||
|
|
||||||
|
p-debounce@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-debounce/-/p-debounce-3.0.1.tgz#51c38b03aa09f319ec507f1d8aba831949c8bbf2"
|
||||||
|
integrity sha512-7n7FWY/f4gmVkd6BwC2EZRbTnAmZbL/Zdrc3qbJRnwkb3OUp4HbPlEN1XybpQk0MML6RDDdePMFIr4dOXXfPNw==
|
||||||
|
|
||||||
|
parseurl@~1.3.3:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz"
|
||||||
|
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||||
|
|
||||||
|
path-to-regexp@0.1.7:
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
|
||||||
|
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||||
|
|
||||||
|
pbkdf2-wrapper@^1.3.2:
|
||||||
|
version "1.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/pbkdf2-wrapper/-/pbkdf2-wrapper-1.3.2.tgz"
|
||||||
|
integrity sha512-McL8NfgXcIsLewiKd8MS4vQO+Q0JuQ7fxEAOIIKs/FJt49fnuJDRG6nkSp0TpXVjRydTllmhALFfPckc3zcA8w==
|
||||||
|
dependencies:
|
||||||
|
righto "^6.1.3"
|
||||||
|
|
||||||
|
pify@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz"
|
||||||
|
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
|
||||||
|
|
||||||
|
proxy-addr@~2.0.5:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz"
|
||||||
|
integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
|
||||||
|
dependencies:
|
||||||
|
forwarded "~0.1.2"
|
||||||
|
ipaddr.js "1.9.1"
|
||||||
|
|
||||||
|
qs@6.7.0:
|
||||||
|
version "6.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz"
|
||||||
|
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||||
|
|
||||||
|
range-parser@~1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz"
|
||||||
|
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||||
|
|
||||||
|
raw-body@2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz"
|
||||||
|
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
http-errors "1.7.2"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
righto@^6.1.3:
|
||||||
|
version "6.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/righto/-/righto-6.1.3.tgz"
|
||||||
|
integrity sha512-tfnK3e10FjBCKSfVI69vJCzSCsHNaxCK7pdEhnxGM89KxHm4ykxT5B1jq6Xoj12+vK1atUvcKwAIFG84IBrPLw==
|
||||||
|
dependencies:
|
||||||
|
abbott "^1.1.3"
|
||||||
|
setimmediate "^1.0.5"
|
||||||
|
|
||||||
|
safe-buffer@5.1.2:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz"
|
||||||
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
|
"safer-buffer@>= 2.1.2 < 3":
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz"
|
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
|
send@0.17.1:
|
||||||
|
version "0.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz"
|
||||||
|
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
destroy "~1.0.4"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
fresh "0.5.2"
|
||||||
|
http-errors "~1.7.2"
|
||||||
|
mime "1.6.0"
|
||||||
|
ms "2.1.1"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
statuses "~1.5.0"
|
||||||
|
|
||||||
|
serve-static@1.14.1:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz"
|
||||||
|
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
|
||||||
|
dependencies:
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
send "0.17.1"
|
||||||
|
|
||||||
|
setimmediate@^1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz"
|
||||||
|
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
|
||||||
|
|
||||||
|
setprototypeof@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz"
|
||||||
|
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||||
|
|
||||||
|
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz"
|
||||||
|
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||||
|
|
||||||
|
steno@^0.4.1:
|
||||||
|
version "0.4.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz"
|
||||||
|
integrity sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs=
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.1.3"
|
||||||
|
|
||||||
|
toidentifier@1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz"
|
||||||
|
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||||
|
|
||||||
|
type-is@~1.6.17, type-is@~1.6.18:
|
||||||
|
version "1.6.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz"
|
||||||
|
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||||
|
dependencies:
|
||||||
|
media-typer "0.3.0"
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
|
||||||
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz"
|
||||||
|
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
||||||
|
|
||||||
|
utils-merge@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz"
|
||||||
|
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||||
|
|
||||||
|
vary@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz"
|
||||||
|
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
1
commitlint.config.js
Normal file
1
commitlint.config.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module.exports = { extends: ["@commitlint/config-conventional"] };
|
|
@ -1,15 +1,15 @@
|
||||||
version: "3"
|
version: "3.9"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
zerotier:
|
zerotier:
|
||||||
image: zyclonite/zerotier:1.10.6
|
image: dec0dos/zerotier-controller:latest
|
||||||
container_name: zu-controller
|
container_name: zu-controller
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./docker/zerotier/Dockerfile
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ./zerotier-one:/var/lib/zerotier-one
|
- controller_data:/var/lib/zerotier-one
|
||||||
environment:
|
|
||||||
- ZT_OVERRIDE_LOCAL_CONF=true
|
|
||||||
- ZT_ALLOW_MANAGEMENT_FROM=0.0.0.0/0
|
|
||||||
expose:
|
expose:
|
||||||
- "9993/tcp"
|
- "9993/tcp"
|
||||||
ports:
|
ports:
|
||||||
|
@ -24,8 +24,8 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- zerotier
|
- zerotier
|
||||||
volumes:
|
volumes:
|
||||||
- ./zerotier-one:/var/lib/zerotier-one
|
- controller_data:/var/lib/zerotier-one
|
||||||
- ./data:/app/backend/data
|
- zero-ui_data:/app/backend/data
|
||||||
environment:
|
environment:
|
||||||
- ZU_CONTROLLER_ENDPOINT=http://zerotier:9993/
|
- ZU_CONTROLLER_ENDPOINT=http://zerotier:9993/
|
||||||
- ZU_SECURE_HEADERS=true
|
- ZU_SECURE_HEADERS=true
|
||||||
|
@ -39,9 +39,14 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- zero-ui
|
- zero-ui
|
||||||
command: caddy reverse-proxy --from YOURDOMAIN.com --to zero-ui:4000
|
command: caddy reverse-proxy --from example.com --to zero-ui:4000
|
||||||
volumes:
|
volumes:
|
||||||
- ./caddy:/data/caddy
|
- caddy_data:/data
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
zero-ui_data:
|
||||||
|
controller_data:
|
||||||
|
caddy_data:
|
||||||
|
|
|
@ -1,68 +1,32 @@
|
||||||
# Stage 1: Build frontend
|
FROM node:current-alpine3.13 as build-stage
|
||||||
FROM --platform=$BUILDPLATFORM node:lts-alpine AS frontend-build
|
|
||||||
|
|
||||||
|
ENV INLINE_RUNTIME_CHUNK=false
|
||||||
ENV GENERATE_SOURCEMAP=false
|
ENV GENERATE_SOURCEMAP=false
|
||||||
|
|
||||||
# Enable corepack and create necessary directories in one layer
|
|
||||||
RUN corepack enable && mkdir -p /app/frontend
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy package-related files and install dependencies
|
|
||||||
COPY tsconfig.json package.json yarn.lock* .yarnrc.yml ./
|
|
||||||
COPY .yarn/ ./.yarn
|
|
||||||
|
|
||||||
# Set working directory to frontend and copy package files
|
|
||||||
WORKDIR /app/frontend
|
WORKDIR /app/frontend
|
||||||
COPY ./frontend/package*.json /app/frontend/
|
COPY ./frontend/package*.json /app/frontend
|
||||||
|
COPY ./frontend/yarn.lock /app/frontend
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
# Install frontend dependencies and build the frontend
|
COPY ./frontend /app/frontend
|
||||||
RUN yarn workspaces focus frontend
|
|
||||||
COPY ./frontend /app/frontend/
|
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
# Stage 2: Build backend
|
|
||||||
FROM node:lts-alpine AS backend-build
|
|
||||||
|
|
||||||
# Enable corepack and create necessary directories in one layer
|
FROM node:current-alpine3.13
|
||||||
RUN corepack enable && mkdir -p /app/backend
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app/frontend/build
|
||||||
|
COPY --from=build-stage /app/frontend/build /app/frontend/build/
|
||||||
|
|
||||||
# Copy package-related files and install dependencies
|
|
||||||
COPY package.json yarn.lock* .yarnrc.yml ./
|
|
||||||
COPY .yarn/ ./.yarn
|
|
||||||
|
|
||||||
# Set working directory to backend and copy package files
|
|
||||||
WORKDIR /app/backend
|
WORKDIR /app/backend
|
||||||
COPY ./backend/package*.json /app/backend/
|
COPY ./backend/package*.json /app/backend
|
||||||
|
COPY ./backend/yarn.lock /app/backend
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
# Install backend dependencies
|
|
||||||
RUN yarn workspaces focus --production backend && yarn cache clean
|
|
||||||
|
|
||||||
# Copy the backend source files
|
|
||||||
COPY ./backend /app/backend
|
COPY ./backend /app/backend
|
||||||
|
|
||||||
# Final Stage: Production
|
EXPOSE 4000
|
||||||
FROM node:lts-alpine
|
|
||||||
|
|
||||||
# Set the working directory to /app/backend
|
|
||||||
WORKDIR /app/backend
|
|
||||||
|
|
||||||
# Copy the built frontend from the frontend-build stage
|
|
||||||
COPY --from=frontend-build /app/frontend/build /app/frontend/build
|
|
||||||
|
|
||||||
# Copy the backend files from the backend-build stage
|
|
||||||
COPY --from=backend-build /app/backend /app/backend
|
|
||||||
COPY --from=backend-build /app/node_modules /app/backend/node_modules
|
|
||||||
|
|
||||||
# Environment variables
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV ZU_SECURE_HEADERS=true
|
ENV ZU_SECURE_HEADERS=true
|
||||||
ENV ZU_SERVE_FRONTEND=true
|
ENV ZU_SERVE_FRONTEND=true
|
||||||
|
|
||||||
# Expose the application port
|
CMD [ "node", "./bin/www" ]
|
||||||
EXPOSE 4000
|
|
||||||
|
|
||||||
# Start the application
|
|
||||||
CMD ["node", "bin/www.js"]
|
|
||||||
|
|
9
docker/zerotier/Dockerfile
Normal file
9
docker/zerotier/Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
RUN apk add --no-cache zerotier-one
|
||||||
|
RUN echo "{\"settings\": {\"portMappingEnabled\": true,\"softwareUpdate\": \"disable\",\"allowManagementFrom\": [\"0.0.0.0/0\"]}}" > /var/lib/zerotier-one/local.conf
|
||||||
|
|
||||||
|
EXPOSE 9993/tcp
|
||||||
|
EXPOSE 9993/udp
|
||||||
|
|
||||||
|
ENTRYPOINT ["zerotier-one"]
|
|
@ -2,15 +2,16 @@
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
If there are any vulnerabilities in ZeroUI, don't hesitate to _report them_.
|
If there are any vulnerability in **ZeroUI** project, don't hesitate to _report them_.
|
||||||
|
|
||||||
1. Use any of the [private contact addresses](https://github.com/dec0dOS/zero-ui#support).
|
1. Use any of the [contact addresses](https://github.com/dec0dOS/zero-ui#support).
|
||||||
2. Describe the vulnerability.
|
2. Describe the vulnerability.
|
||||||
|
|
||||||
- If you have a fix, that is most welcome - please attach or summarize it in your message!
|
- If you have a fix, explain or attach it.
|
||||||
|
- In the near time, expect a reply with the required steps. Also, there may be a demand for a pull request which include the fixes.
|
||||||
|
|
||||||
3. We will evaluate the vulnerability and, if necessary, release a fix or mitigating steps to address it. We will contact you to let you know the outcome, and will credit you in the report.
|
##### You should not disclose the vulnerability publicly if you haven't received an answer in some weeks.
|
||||||
|
|
||||||
- Please **do not disclose the vulnerability publicly** until a fix is released!
|
##### If the vulnerability is rejected, you may post it publicly within some hour of rejection, unless the rejection is withdrawn within that time period.
|
||||||
|
|
||||||
4. Once we have either a) published a fix, or b) declined to address the vulnerability for whatever reason, you are free to publicly disclose it.
|
##### After the vulnerability has been fixed, you may disclose the vulnerability details publicly over some days.
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 162 KiB |
Binary file not shown.
Before Width: | Height: | Size: 477 KiB After Width: | Height: | Size: 834 KiB |
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"root": true,
|
|
||||||
"env": { "browser": true, "es2020": true },
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:react/jsx-runtime",
|
|
||||||
"plugin:react-hooks/recommended"
|
|
||||||
],
|
|
||||||
"ignorePatterns": ["dist", ".eslintrc.cjs"],
|
|
||||||
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
|
|
||||||
"settings": { "react": { "version": "17.0.2" } },
|
|
||||||
"plugins": ["react-refresh"],
|
|
||||||
"rules": {
|
|
||||||
"react-refresh/only-export-components": [
|
|
||||||
"warn",
|
|
||||||
{ "allowConstantExport": true }
|
|
||||||
],
|
|
||||||
"react/prop-types": ["off"],
|
|
||||||
"no-unused-vars": ["off"]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
||||||
/>
|
|
||||||
<meta name="description" content="ZeroUI" />
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
<title>ZeroUI</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/index.jsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
10
frontend/jsconfig.json
Normal file
10
frontend/jsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "**/node_modules/*"],
|
||||||
|
"typeAcquisition": {
|
||||||
|
"exclude": ["dotenv", "harmony-reflect"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,56 +1,48 @@
|
||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "zero-ui-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "^4.5.8",
|
"@fontsource/roboto": "^4.2.2",
|
||||||
"@material-ui/core": "^4.12.4",
|
"@material-ui/core": "^4.11.3",
|
||||||
"@material-ui/icons": "^4.11.3",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/styles": "^4.11.5",
|
"@uiw/react-codemirror": "^3.0.5",
|
||||||
"@uiw/react-codemirror": "^3.1.0",
|
"axios": "^0.21.1",
|
||||||
"axios": "^0.27.2",
|
"history": "^5.0.0",
|
||||||
"codemirror": "^5.62.3",
|
"ipaddr.js": "^2.0.0",
|
||||||
"date-fns": "^2.29.2",
|
|
||||||
"history": "^5.3.0",
|
|
||||||
"i18next": "^23.5.1",
|
|
||||||
"i18next-browser-languagedetector": "^7.1.0",
|
|
||||||
"i18next-http-backend": "^2.2.2",
|
|
||||||
"ipaddr.js": "^2.0.1",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.1",
|
||||||
"react-data-table-component": "^6.11.8",
|
"react-data-table-component": "^6.11.7",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.1",
|
||||||
"react-i18next": "^13.3.0",
|
|
||||||
"react-is": "^17.0.2",
|
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-use": "^17.4.0",
|
"react-scripts": "4.0.3",
|
||||||
"styled-components": "^5.3.11"
|
"react-use": "^17.2.1",
|
||||||
|
"styled-components": "^5.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/codemirror": "^5.60.10",
|
"source-map-explorer": "^2.5.2"
|
||||||
"@types/lodash": "^4.14.199",
|
|
||||||
"@types/react": "^17.0.67",
|
|
||||||
"@types/react-dom": "^17.0.21",
|
|
||||||
"@types/react-is": "^17.0.5",
|
|
||||||
"@types/react-router-dom": "^5.3.3",
|
|
||||||
"@types/styled-components": "^5.1.28",
|
|
||||||
"@vitejs/plugin-react": "^4.1.0",
|
|
||||||
"eslint": "^8.51.0",
|
|
||||||
"eslint-plugin-react": "^7.33.2",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.3",
|
|
||||||
"rimraf": "^5.0.5",
|
|
||||||
"source-map-explorer": "^2.5.3",
|
|
||||||
"typescript": "^5.2.2",
|
|
||||||
"vite": "^4.4.11",
|
|
||||||
"vite-plugin-static-copy": "^1.0.6"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "BROWSER=none react-scripts start",
|
||||||
"build": "vite build",
|
"build": "react-scripts build",
|
||||||
"serve": "vite preview",
|
"analyze": "source-map-explorer 'build/static/js/*.js'"
|
||||||
"clean": "rimraf build",
|
},
|
||||||
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
"homepage": "/app",
|
||||||
"typecheck": "tsc --pretty --noEmit -p tsconfig.json",
|
"proxy": "http://localhost:4000",
|
||||||
"analyze": "vite build --sourcemap true && source-map-explorer 'build/assets/*.js' --no-border-checks"
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
frontend/public/index.html
Normal file
19
frontend/public/index.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<base href="%PUBLIC_URL%/">
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="ZeroUI"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<title>ZeroUI</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"flowRules": "Flow Rules",
|
|
||||||
"createNetwork": "Create A Network",
|
|
||||||
"createOneNetwork": "Please create at least one network",
|
|
||||||
"controllerNetworks": "Controller networks",
|
|
||||||
"network_one": "Network",
|
|
||||||
"network_other": "Networks",
|
|
||||||
"controllerAddress": "Network controller address",
|
|
||||||
"loginToContinue": "Please, Log In to continue",
|
|
||||||
"zerouiDesc": "ZeroUI - ZeroTier Controller Web UI - is a web user interface for a self-hosted ZeroTier network controller.",
|
|
||||||
"logIn": "Log In",
|
|
||||||
"logInToken": "Token Log In",
|
|
||||||
"cancel": "Cancel",
|
|
||||||
"management": "Management",
|
|
||||||
"deleteNetwork": "Delete Network",
|
|
||||||
"deleteAlert": "This action cannot be undone.",
|
|
||||||
"deleteNetworkConfirm": "Are you sure you want to delete this network?",
|
|
||||||
"deleteMemberConfirm": "Are you sure you want to delete this member?",
|
|
||||||
"delete": "Delete",
|
|
||||||
"logOut": "Log out",
|
|
||||||
"advancedFeature": "ADVANCED FEATURE",
|
|
||||||
"noDevices": "No devices have joined this network. Use the app on your devices to join",
|
|
||||||
"member_one": "Member",
|
|
||||||
"member_other": "Members",
|
|
||||||
"addMemberManually": "Manually Add Member",
|
|
||||||
"name": "Name",
|
|
||||||
"description": "Description",
|
|
||||||
"allowBridging": "Allow Ethernet Bridging",
|
|
||||||
"noAutoIP": "Do Not Auto-Assign IPs",
|
|
||||||
"capabilities": "Capabilities",
|
|
||||||
"noCapDef": "No capabilities defined",
|
|
||||||
"tags": "Tags",
|
|
||||||
"noTagDef": "No tags defined",
|
|
||||||
"authorized": "Authorized",
|
|
||||||
"address": "Address",
|
|
||||||
"managedIPs": "Managed IPs",
|
|
||||||
"lastSeen": "Last seen",
|
|
||||||
"version": "Version",
|
|
||||||
"physIp": "Physical IP",
|
|
||||||
"latency": "Latency",
|
|
||||||
"settings": "Settings",
|
|
||||||
"generalSettings": "General settings",
|
|
||||||
"networkId": "Network ID",
|
|
||||||
"accessControl": "Access control",
|
|
||||||
"public": "Public",
|
|
||||||
"private": "Private",
|
|
||||||
"managedRoutes": "Managed routes",
|
|
||||||
"addRoute": "Add route",
|
|
||||||
"target": "Target",
|
|
||||||
"via": "Via",
|
|
||||||
"start": "Start",
|
|
||||||
"end": "End",
|
|
||||||
"ipv4AutoAssign": "IPv4 Auto-Assign",
|
|
||||||
"autoAssignPool": "IPv4 Auto-Assign",
|
|
||||||
"addIPv4Pool": "Add IPv4 Pool",
|
|
||||||
"multicastLimit": "Multicast Recipient Limit",
|
|
||||||
"enableBroadcast": "Enable Broadcast",
|
|
||||||
"logInFailed": "Invalid username or password",
|
|
||||||
"tooManyAttempts": "Too many login attempts, please try again in 15 minutes.",
|
|
||||||
"language": "Language",
|
|
||||||
"notAuthorized": "You are not authorized. Please Log In.",
|
|
||||||
"saveChanges": "Save changes",
|
|
||||||
"optional": "Optional",
|
|
||||||
"destination": "Destination",
|
|
||||||
"username": "Username",
|
|
||||||
"password": "Password",
|
|
||||||
"languageName": "English"
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"flowRules": "Reglas de flujo",
|
|
||||||
"createNetwork": "Crear una red",
|
|
||||||
"createOneNetwork": "Por favor, crea al menos una red",
|
|
||||||
"controllerNetworks": "Controlador de redes",
|
|
||||||
"network_one": "Red",
|
|
||||||
"network_other": "Redes",
|
|
||||||
"controllerAddress": "Dirección del controlador",
|
|
||||||
"loginToContinue": "Por favor, inicia sesión para continuar",
|
|
||||||
"zerouiDesc": "ZeroUI - ZeroTier Controller Web UI - es una interfaz de usuario web para un controlador de red ZeroTier self-hosted.",
|
|
||||||
"logIn": "Iniciar sesión",
|
|
||||||
"logInToken": "Iniciar sesión con token",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"management": "Gestión",
|
|
||||||
"deleteNetwork": "Borrar red",
|
|
||||||
"deleteAlert": "Esta acción no puede ser revertida.",
|
|
||||||
"deleteNetworkConfirm": "¿Seguro que deseas borrar esta red?",
|
|
||||||
"deleteMemberConfirm": "¿Seguro que deseas borrar este usuario?",
|
|
||||||
"delete": "Borrar",
|
|
||||||
"logOut": "Cerrar sesión",
|
|
||||||
"advancedFeature": "CARACTERÍSTICA AVANZADA",
|
|
||||||
"noDevices": "Ningún dispositivo se ha unido a esta red. Utilice la aplicación en sus dispositivos para unirse",
|
|
||||||
"member_one": "Miembro",
|
|
||||||
"member_other": "Miembros",
|
|
||||||
"addMemberManually": "Añadir miembro manualmente",
|
|
||||||
"name": "Nombre",
|
|
||||||
"description": "Descripción",
|
|
||||||
"allowBridging": "Permitir puente Ethernet",
|
|
||||||
"noAutoIP": "No autoasignar IPs",
|
|
||||||
"capabilities": "Permisos",
|
|
||||||
"noCapDef": "No hay permisos definidos",
|
|
||||||
"tags": "Etiquetas",
|
|
||||||
"noTagDef": "No hay etiquetas definidas",
|
|
||||||
"authorized": "Autorizado",
|
|
||||||
"address": "Dirección",
|
|
||||||
"managedIPs": "IPs asignadas",
|
|
||||||
"lastSeen": "Visto por última vez",
|
|
||||||
"version": "Versión",
|
|
||||||
"physIp": "IP pública",
|
|
||||||
"latency": "Latencia",
|
|
||||||
"settings": "Ajustes",
|
|
||||||
"generalSettings": "Ajustes generales",
|
|
||||||
"networkId": "ID de red",
|
|
||||||
"accessControl": "Control de acceso",
|
|
||||||
"public": "Público",
|
|
||||||
"private": "Privado",
|
|
||||||
"managedRoutes": "Rutas gestionadas",
|
|
||||||
"addRoute": "Añadir ruta",
|
|
||||||
"target": "Objetivo",
|
|
||||||
"via": "Vía",
|
|
||||||
"start": "Inicio",
|
|
||||||
"end": "Final",
|
|
||||||
"autoAssignPool": "Rango de IPv4 autoasignables",
|
|
||||||
"ipv4AutoAssign": "Rangos de IPv4 automáticos",
|
|
||||||
"addIPv4Pool": "Añadir rango IPv4",
|
|
||||||
"multicastLimit": "Límite de destinatarios multicast",
|
|
||||||
"enableBroadcast": "Habilitar broadcast",
|
|
||||||
"logInFailed": "Nombre de usuario o contraseña incorrecto",
|
|
||||||
"tooManyAttempts": "Demasiados intentos de inicio de sesión. Vuelvee a intentarlo en 15 minutos",
|
|
||||||
"language": "Idioma",
|
|
||||||
"notAuthorized": "No estás autorizado. Por favor, inicia sesión.",
|
|
||||||
"saveChanges": "Guardar cambios",
|
|
||||||
"optional": "Opcional",
|
|
||||||
"destination": "Destino",
|
|
||||||
"username": "Nombre de usuario",
|
|
||||||
"password": "Contraseña",
|
|
||||||
"languageName": "Español"
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
"flowRules": "Правила потока",
|
|
||||||
"createNetwork": "Создать сеть",
|
|
||||||
"createOneNetwork": "Пожалуйста, создайте хотя бы одну сеть",
|
|
||||||
"controllerNetworks": "Сети контроллера",
|
|
||||||
"network_one": "Сеть",
|
|
||||||
"network_few": "Сети",
|
|
||||||
"network_many": "Сетей",
|
|
||||||
"network_other": "Сетей",
|
|
||||||
"controllerAddress": "Адрес контроллера сети",
|
|
||||||
"loginToContinue": "Пожалуйста, войдите, чтобы продолжить",
|
|
||||||
"zerouiDesc": "ZeroUI - Веб-интерфейс контроллера ZeroTier - это веб-интерфейс для самостоятельного хостинга контроллера сети ZeroTier.",
|
|
||||||
"logIn": "Войти",
|
|
||||||
"logInToken": "Войти по токену",
|
|
||||||
"cancel": "Отмена",
|
|
||||||
"management": "Управление",
|
|
||||||
"deleteNetwork": "Удалить сеть",
|
|
||||||
"deleteAlert": "Это действие не может быть отменено.",
|
|
||||||
"deleteNetworkConfirm": "Вы уверены, что хотите удалить эту сеть?",
|
|
||||||
"deleteMemberConfirm": "Вы уверены, что хотите удалить это устройство?",
|
|
||||||
"delete": "Удалить",
|
|
||||||
"logOut": "Выйти",
|
|
||||||
"advancedFeature": "РАСШИРЕННАЯ ФУНКЦИЯ",
|
|
||||||
"noDevices": "Ни одно устройство не присоединилось к этой сети. Используйте приложение на ваших устройствах для подключения",
|
|
||||||
"member_one": "Устройство",
|
|
||||||
"member_few": "Устройства",
|
|
||||||
"member_many": "Устройства",
|
|
||||||
"member_other": "Устройств",
|
|
||||||
"addMemberManually": "Добавить устройство вручную",
|
|
||||||
"name": "Имя",
|
|
||||||
"description": "Описание",
|
|
||||||
"allowBridging": "Разрешить Ethernet мост",
|
|
||||||
"noAutoIP": "Не назначать IP автоматически",
|
|
||||||
"capabilities": "Возможности",
|
|
||||||
"noCapDef": "Нет определенных возможностей",
|
|
||||||
"tags": "Теги",
|
|
||||||
"noTagDef": "Нет определенных тегов",
|
|
||||||
"authorized": "Авторизован",
|
|
||||||
"address": "Адрес",
|
|
||||||
"managedIPs": "Управляемые IP",
|
|
||||||
"lastSeen": "Последний раз был онлайн",
|
|
||||||
"version": "Версия",
|
|
||||||
"physIp": "Физический IP",
|
|
||||||
"latency": "Задержка",
|
|
||||||
"settings": "Настройки",
|
|
||||||
"generalSettings": "Общие настройки",
|
|
||||||
"networkId": "ID сети",
|
|
||||||
"accessControl": "Контроль доступа",
|
|
||||||
"public": "Публичный",
|
|
||||||
"private": "Частный",
|
|
||||||
"managedRoutes": "Управляемые маршруты",
|
|
||||||
"addRoute": "Добавить маршрут",
|
|
||||||
"target": "Цель",
|
|
||||||
"via": "Через",
|
|
||||||
"start": "Начало",
|
|
||||||
"end": "Конец",
|
|
||||||
"ipv4AutoAssign": "Автоматическое назначение IPv4",
|
|
||||||
"autoAssignPool": "Автоматическое назначение IPv4",
|
|
||||||
"addIPv4Pool": "Добавить пул IPv4",
|
|
||||||
"multicastLimit": "Ограничение получателей мультикаста",
|
|
||||||
"enableBroadcast": "Включить широковещание",
|
|
||||||
"logInFailed": "Неверное имя пользователя или пароль",
|
|
||||||
"tooManyAttempts": "Слишком много попыток входа, попробуйте снова через 15 минут.",
|
|
||||||
"language": "Язык",
|
|
||||||
"notAuthorized": "Вы не авторизованы. Пожалуйста, войдите.",
|
|
||||||
"saveChanges": "Сохранить изменения",
|
|
||||||
"optional": "Необязательно",
|
|
||||||
"destination": "Назначение",
|
|
||||||
"username": "Имя пользователя",
|
|
||||||
"password": "Пароль",
|
|
||||||
"languageName": "Русский"
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
{
|
|
||||||
"flowRules": "流量规则",
|
|
||||||
"createNetwork": "创建网络",
|
|
||||||
"createOneNetwork": "请至少创建一个网络",
|
|
||||||
"controllerNetworks": "控制器网络",
|
|
||||||
"network_one": "网络",
|
|
||||||
"network_other": "网络们",
|
|
||||||
"controllerAddress": "网络控制器地址",
|
|
||||||
"loginToContinue": "请登录以继续",
|
|
||||||
"zerouiDesc": "ZeroUI - ZeroTier 控制器Web界面 - 是一个用于自托管ZeroTier网络控制器的Web用户界面。",
|
|
||||||
"logIn": "登录",
|
|
||||||
"logInToken": "令牌登录",
|
|
||||||
"cancel": "取消",
|
|
||||||
"management": "管理",
|
|
||||||
"deleteNetwork": "删除网络",
|
|
||||||
"deleteAlert": "此操作无法撤销。",
|
|
||||||
"deleteNetworkConfirm": "您确定要删除此网络吗?",
|
|
||||||
"deleteMemberConfirm": "您确定要删除此成员吗?",
|
|
||||||
"delete": "删除",
|
|
||||||
"logOut": "登出",
|
|
||||||
"advancedFeature": "高级功能",
|
|
||||||
"noDevices": "没有设备加入此网络。请使用您的设备上的应用程序加入。",
|
|
||||||
"member_one": "成员",
|
|
||||||
"member_other": "成员们",
|
|
||||||
"addMemberManually": "手动添加成员",
|
|
||||||
"name": "名称",
|
|
||||||
"description": "描述",
|
|
||||||
"allowBridging": "允许以太网桥接",
|
|
||||||
"noAutoIP": "不自动分配IP",
|
|
||||||
"capabilities": "能力",
|
|
||||||
"noCapDef": "未定义能力",
|
|
||||||
"tags": "标签",
|
|
||||||
"noTagDef": "未定义标签",
|
|
||||||
"authorized": "已授权",
|
|
||||||
"address": "地址",
|
|
||||||
"managedIPs": "管理的IP地址",
|
|
||||||
"lastSeen": "最后看到的时间",
|
|
||||||
"version": "版本",
|
|
||||||
"physIp": "物理IP",
|
|
||||||
"latency": "延迟",
|
|
||||||
"settings": "设置",
|
|
||||||
"generalSettings": "常规设置",
|
|
||||||
"networkId": "网络ID",
|
|
||||||
"accessControl": "访问控制",
|
|
||||||
"public": "公共",
|
|
||||||
"private": "私有",
|
|
||||||
"managedRoutes": "管理的路由",
|
|
||||||
"addRoute": "添加路由",
|
|
||||||
"target": "目标",
|
|
||||||
"via": "通过",
|
|
||||||
"start": "开始",
|
|
||||||
"end": "结束",
|
|
||||||
"ipv4AutoAssign": "IPv4 自动分配",
|
|
||||||
"autoAssignPool": "IPv4 自动分配池",
|
|
||||||
"addIPv4Pool": "添加IPv4池",
|
|
||||||
"multicastLimit": "多播接收者限制",
|
|
||||||
"enableBroadcast": "启用广播",
|
|
||||||
"logInFailed": "无效的用户名或密码",
|
|
||||||
"tooManyAttempts": "尝试登录次数过多,请15分钟后重试。",
|
|
||||||
"language": "语言",
|
|
||||||
"notAuthorized": "您没有权限。请登录。",
|
|
||||||
"saveChanges": "保存更改",
|
|
||||||
"optional": "可选",
|
|
||||||
"destination": "目的地",
|
|
||||||
"username": "用户名",
|
|
||||||
"password": "密码",
|
|
||||||
"languageName": "中文"
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
import "@fontsource/roboto";
|
import "@fontsource/roboto";
|
||||||
|
|
||||||
import { Suspense } from "react";
|
|
||||||
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
||||||
|
|
||||||
import Theme from "./components/Theme";
|
import Theme from "./components/Theme";
|
||||||
|
@ -9,27 +8,19 @@ import Bar from "./components/Bar";
|
||||||
import Home from "./routes/Home";
|
import Home from "./routes/Home";
|
||||||
import NotFound from "./routes/NotFound";
|
import NotFound from "./routes/NotFound";
|
||||||
import Network from "./routes/Network/Network";
|
import Network from "./routes/Network/Network";
|
||||||
import Settings from "./routes/Settings";
|
|
||||||
|
|
||||||
import Loading from "./components/Loading";
|
|
||||||
|
|
||||||
import "./i18n";
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Theme>
|
<Theme>
|
||||||
<Suspense fallback={<Loading />}>
|
<BrowserRouter basename="/app">
|
||||||
<BrowserRouter basename="/app">
|
<Bar />
|
||||||
<Bar />
|
<Switch>
|
||||||
<Switch>
|
<Route exact path="/" component={Home} />
|
||||||
<Route exact path="/" component={Home} />
|
<Route path="/network/:nwid" component={Network} />
|
||||||
<Route path="/network/:nwid" component={Network} />
|
<Route path="/404" component={NotFound} />
|
||||||
<Route path="/settings" component={Settings} />
|
<Redirect to="/404" />
|
||||||
<Route path="/404" component={NotFound} />
|
</Switch>
|
||||||
<Redirect to="/404" />
|
</BrowserRouter>
|
||||||
</Switch>
|
|
||||||
</BrowserRouter>
|
|
||||||
</Suspense>
|
|
||||||
</Theme>
|
</Theme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,8 @@ import MenuIcon from "@material-ui/icons/Menu";
|
||||||
|
|
||||||
import LogIn from "components/LogIn";
|
import LogIn from "components/LogIn";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function Bar() {
|
function Bar() {
|
||||||
const [loggedIn, setLoggedIn] = useLocalStorage("loggedIn", false);
|
const [loggedIn, setLoggedIn] = useLocalStorage("loggedIn", false);
|
||||||
const [disabledAuth] = useLocalStorage("disableAuth", false);
|
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -43,23 +40,17 @@ function Bar() {
|
||||||
history.go(0);
|
history.go(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
// TODO: add settings page
|
// TODO: add settings page
|
||||||
|
// {
|
||||||
|
// name: "Settings",
|
||||||
|
// to: "/settings",
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
name: t("settings"),
|
name: "Log out",
|
||||||
to: "/settings",
|
divide: true,
|
||||||
|
onClick: onLogOutClick,
|
||||||
},
|
},
|
||||||
...(!disabledAuth
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: t("logOut"),
|
|
||||||
divide: true,
|
|
||||||
onClick: onLogOutClick,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -81,7 +72,8 @@ function Bar() {
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{loggedIn && menuItems.length > 0 && (
|
|
||||||
|
{loggedIn && (
|
||||||
<>
|
<>
|
||||||
<Button color="inherit" onClick={openMenu}>
|
<Button color="inherit" onClick={openMenu}>
|
||||||
<MenuIcon></MenuIcon>
|
<MenuIcon></MenuIcon>
|
||||||
|
@ -94,7 +86,7 @@ function Bar() {
|
||||||
>
|
>
|
||||||
{menuItems.map((menuItem, index) => {
|
{menuItems.map((menuItem, index) => {
|
||||||
if (
|
if (
|
||||||
Object.prototype.hasOwnProperty.call(menuItem, "condition") &&
|
menuItem.hasOwnProperty("condition") &&
|
||||||
!menuItem.condition
|
!menuItem.condition
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -119,6 +111,7 @@ function Bar() {
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
|
||||||
menuItem.onClick();
|
menuItem.onClick();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,8 +9,6 @@ import NetworkButton from "./components/NetworkButton";
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
import { generateNetworkConfig } from "utils/NetworkConfig";
|
import { generateNetworkConfig } from "utils/NetworkConfig";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function HomeLoggedIn() {
|
function HomeLoggedIn() {
|
||||||
const [networks, setNetworks] = useState([]);
|
const [networks, setNetworks] = useState([]);
|
||||||
|
|
||||||
|
@ -32,8 +30,6 @@ function HomeLoggedIn() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -42,19 +38,19 @@ function HomeLoggedIn() {
|
||||||
className={classes.createBtn}
|
className={classes.createBtn}
|
||||||
onClick={createNetwork}
|
onClick={createNetwork}
|
||||||
>
|
>
|
||||||
{t("createNetwork")}
|
Create A Network
|
||||||
</Button>
|
</Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Grid container spacing={3} className={classes.container}>
|
<Grid container spacing={3} className={classes.container}>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
<Typography variant="h5">{t("controllerNetworks")}</Typography>
|
<Typography variant="h5">Controller networks</Typography>
|
||||||
{networks[0] && t("controllerAddress")}
|
{networks[0] && "Network controller address"}
|
||||||
<Box fontWeight="fontWeightBold">
|
<Box fontWeight="fontWeightBold">
|
||||||
{networks[0] && networks[0]["id"].slice(0, 10)}
|
{networks[0] && networks[0]["id"].slice(0, 10)}
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs="auto">
|
<Grid item xs="auto">
|
||||||
<Typography>{t("network", { count: networks.length })}</Typography>
|
<Typography>Networks</Typography>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
{networks[0] ? (
|
{networks[0] ? (
|
||||||
networks.map((network) => (
|
networks.map((network) => (
|
||||||
|
@ -63,7 +59,7 @@ function HomeLoggedIn() {
|
||||||
</Grid>
|
</Grid>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div>{t("createOneNetwork")}</div>
|
<div>Please create at least one network</div>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
.netBtn {
|
.netBtn {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
max-height: 50px;
|
max-height: 50px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid #b5b5b5;
|
border: 1px solid #b5b5b5;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.netBtn:hover {
|
.netBtn:hover {
|
||||||
transform: translateY(0) scale(1.02);
|
transform: translateY(0) scale(1.02);
|
||||||
background: rgba(0, 0, 0, 0);
|
background: rgba(0,0,0,0);
|
||||||
box-shadow: inset 0 0 0 3px #ffc107;
|
box-shadow: inset 0 0 0 3px #ffc107;
|
||||||
}
|
}
|
||||||
|
|
||||||
.netBtn:focus {
|
.netBtn:focus {
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ function NetworkButton({ network }) {
|
||||||
<Hidden mdDown>
|
<Hidden mdDown>
|
||||||
<ListItem className={classes.cidr}>
|
<ListItem className={classes.cidr}>
|
||||||
{network["config"]["ipAssignmentPools"] &&
|
{network["config"]["ipAssignmentPools"] &&
|
||||||
network["config"]["ipAssignmentPools"][0] &&
|
|
||||||
getCIDRAddress(
|
getCIDRAddress(
|
||||||
network["config"]["ipAssignmentPools"][0]["ipRangeStart"],
|
network["config"]["ipAssignmentPools"][0]["ipRangeStart"],
|
||||||
network["config"]["ipAssignmentPools"][0]["ipRangeEnd"]
|
network["config"]["ipAssignmentPools"][0]["ipRangeEnd"]
|
||||||
|
|
|
@ -1,38 +1,6 @@
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Grid, Typography } from "@material-ui/core";
|
import { Grid, Typography } from "@material-ui/core";
|
||||||
import { useLocalStorage } from "react-use";
|
|
||||||
import { useHistory } from "react-router-dom";
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
function HomeLoggedOut() {
|
function HomeLoggedOut() {
|
||||||
const [, setLoggedIn] = useLocalStorage("loggedIn", false);
|
|
||||||
const [, setToken] = useLocalStorage("token", null);
|
|
||||||
const [, setDisableAuth] = useLocalStorage("disableAuth", false);
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchData() {
|
|
||||||
axios
|
|
||||||
.get("/auth/login", { withCredentials: true })
|
|
||||||
.then(function (response) {
|
|
||||||
if (!response.data.enabled) {
|
|
||||||
setLoggedIn(true);
|
|
||||||
setDisableAuth(true);
|
|
||||||
setToken("");
|
|
||||||
history.go(0);
|
|
||||||
} else {
|
|
||||||
setDisableAuth(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fetchData();
|
|
||||||
}, [history, setDisableAuth, setLoggedIn, setToken]);
|
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
|
@ -46,11 +14,14 @@ function HomeLoggedOut() {
|
||||||
>
|
>
|
||||||
<Grid item xs={10}>
|
<Grid item xs={10}>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
<span>{t("zerouiDesc")}</span>
|
<span>
|
||||||
|
ZeroUI - ZeroTier Controller Web UI - is a web user interface for a
|
||||||
|
self-hosted ZeroTier network controller.
|
||||||
|
</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography>
|
<Typography>
|
||||||
<span>{t("loginToContinue")}</span>
|
<span>Please Log In to continue</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { Typography, Box, CircularProgress } from "@material-ui/core";
|
|
||||||
|
|
||||||
import useStyles from "./Loading.styles";
|
|
||||||
|
|
||||||
function Loading() {
|
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.root}>
|
|
||||||
<CircularProgress color="primary" />
|
|
||||||
<Typography variant="h6" component="div" className={classes.loadingText}>
|
|
||||||
Loading
|
|
||||||
<span className="loadingDots"></span>
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Loading;
|
|
|
@ -1,32 +0,0 @@
|
||||||
// Loading.styles.jsx
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
root: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "100vh",
|
|
||||||
},
|
|
||||||
loadingText: {
|
|
||||||
marginTop: "16px",
|
|
||||||
position: "relative",
|
|
||||||
"& .loadingDots::after": {
|
|
||||||
content: '"."',
|
|
||||||
position: "absolute",
|
|
||||||
left: "100%",
|
|
||||||
marginLeft: "4px",
|
|
||||||
animation: "$loadingDots 1s infinite",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"@keyframes loadingDots": {
|
|
||||||
"0%": { content: '"."' },
|
|
||||||
"25%": { content: '".."' },
|
|
||||||
"50%": { content: '"..."' },
|
|
||||||
"75%": { content: '"...."' },
|
|
||||||
"100%": { content: '"."' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default useStyles;
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from "./Loading.jsx";
|
|
|
@ -6,7 +6,7 @@ import LogInToken from "./components/LogInToken";
|
||||||
function LogIn() {
|
function LogIn() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{import.meta.env.DEV && (
|
{process.env.NODE_ENV === "development" && (
|
||||||
<>
|
<>
|
||||||
<LogInToken />
|
<LogInToken />
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
|
|
|
@ -12,8 +12,6 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function LogInToken() {
|
function LogInToken() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [errorText, setErrorText] = useState("");
|
const [errorText, setErrorText] = useState("");
|
||||||
|
@ -43,8 +41,6 @@ function LogInToken() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
const LogIn = () => {
|
const LogIn = () => {
|
||||||
if (token.length !== 32) {
|
if (token.length !== 32) {
|
||||||
setErrorText("Token length error");
|
setErrorText("Token length error");
|
||||||
|
@ -59,12 +55,12 @@ function LogInToken() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
|
<Button onClick={handleClickOpen} color="inherit" variant="outlined">
|
||||||
{t("logInToken")}
|
Token Log In
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
||||||
<DialogTitle>{t("logIn")}</DialogTitle>
|
<DialogTitle>Log In</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>{t("advancedFeature")}</DialogContentText>
|
<DialogContentText>ADVANCED FEATURE.</DialogContentText>
|
||||||
<TextField
|
<TextField
|
||||||
value={token}
|
value={token}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
@ -80,10 +76,10 @@ function LogInToken() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
{t("cancel")}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={LogIn} color="primary">
|
<Button onClick={LogIn} color="primary">
|
||||||
{t("logIn")}
|
Log In
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -13,14 +13,10 @@ import {
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function LogInUser() {
|
function LogInUser() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
|
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
|
@ -69,20 +65,17 @@ function LogInUser() {
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
setPassword("");
|
setPassword("");
|
||||||
setSnackbarOpen(true);
|
setSnackbarOpen(true);
|
||||||
setError(error.response.data.error);
|
console.log(error);
|
||||||
// console.error(error.response.data.error);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={handleClickOpen} color="primary" variant="contained">
|
<Button onClick={handleClickOpen} color="primary" variant="contained">
|
||||||
{t("logIn")}
|
Log In
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
<Dialog open={open} onClose={handleClose} onKeyPress={handleKeyPress}>
|
||||||
<DialogTitle>{t("logIn")}</DialogTitle>
|
<DialogTitle>Log In</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
|
@ -91,7 +84,7 @@ function LogInUser() {
|
||||||
setUsername(e.target.value);
|
setUsername(e.target.value);
|
||||||
}}
|
}}
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label={t("username")}
|
label="username"
|
||||||
type="username"
|
type="username"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
@ -101,17 +94,17 @@ function LogInUser() {
|
||||||
setPassword(e.target.value);
|
setPassword(e.target.value);
|
||||||
}}
|
}}
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label={t("password")}
|
label="password"
|
||||||
type="password"
|
type="password"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
{t("cancel")}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={LogIn} color="primary">
|
<Button onClick={LogIn} color="primary">
|
||||||
{t("logIn")}
|
Log In
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -121,7 +114,7 @@ function LogInUser() {
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "center",
|
horizontal: "center",
|
||||||
}}
|
}}
|
||||||
message={t(error)}
|
message="Invalid username or password"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from "./NetworkManagement";
|
|
|
@ -18,9 +18,7 @@ import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
function NetworkManagment() {
|
||||||
|
|
||||||
function NetworkManagement() {
|
|
||||||
const { nwid } = useParams();
|
const { nwid } = useParams();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
@ -44,12 +42,10 @@ function NetworkManagement() {
|
||||||
history.go(0);
|
history.go(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>{t("management")}</Typography>
|
<Typography>Managment</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Button
|
<Button
|
||||||
|
@ -58,19 +54,21 @@ function NetworkManagement() {
|
||||||
startIcon={<DeleteIcon />}
|
startIcon={<DeleteIcon />}
|
||||||
onClick={handleClickOpen}
|
onClick={handleClickOpen}
|
||||||
>
|
>
|
||||||
{t("deleteNetwork")}
|
Delete Network
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={open} onClose={handleClose}>
|
<Dialog open={open} onClose={handleClose}>
|
||||||
<DialogTitle>{t("deleteNetworkConfirm")}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{"Are you sure you want to delete this network?"}
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>{t("deleteAlert")}</DialogContentText>
|
<DialogContentText>This action cannot be undone.</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
{t("cancel")}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={deleteNetwork} color="secondary">
|
<Button onClick={deleteNetwork} color="secondary">
|
||||||
{t("delete")}
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -79,4 +77,4 @@ function NetworkManagement() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NetworkManagement;
|
export default NetworkManagment;
|
1
frontend/src/components/NetworkManagment/index.jsx
Normal file
1
frontend/src/components/NetworkManagment/index.jsx
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "./NetworkManagment";
|
|
@ -1,29 +1,30 @@
|
||||||
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionDetails,
|
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
|
||||||
Typography,
|
Typography,
|
||||||
|
IconButton,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import DataTable from "react-data-table-component";
|
import DataTable from "react-data-table-component";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
|
import MemberName from "./components/MemberName";
|
||||||
|
import ManagedIP from "./components/ManagedIP";
|
||||||
|
import DeleteMember from "./components/DeleteMember";
|
||||||
|
import MemberSettings from "./components/MemberSettings";
|
||||||
|
import AddMember from "./components/AddMember";
|
||||||
|
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
|
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
|
||||||
import { formatDistance } from "date-fns";
|
|
||||||
import AddMember from "./components/AddMember";
|
|
||||||
import DeleteMember from "./components/DeleteMember";
|
|
||||||
import ManagedIP from "./components/ManagedIP";
|
|
||||||
import MemberName from "./components/MemberName";
|
|
||||||
import MemberSettings from "./components/MemberSettings";
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
function NetworkMembers() {
|
||||||
|
|
||||||
function NetworkMembers({ network }) {
|
|
||||||
const { nwid } = useParams();
|
const { nwid } = useParams();
|
||||||
const [members, setMembers] = useState([]);
|
const [members, setMembers] = useState([]);
|
||||||
|
|
||||||
|
@ -48,30 +49,32 @@ function NetworkMembers({ network }) {
|
||||||
console.log("Action:", req);
|
console.log("Action:", req);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
const handleChange = (
|
||||||
|
member,
|
||||||
|
key1,
|
||||||
|
key2 = null,
|
||||||
|
mode = "text",
|
||||||
|
id = null
|
||||||
|
) => (event) => {
|
||||||
|
const value = parseValue(event, mode, member, key1, key2, id);
|
||||||
|
|
||||||
const handleChange =
|
const updatedMember = replaceValue({ ...member }, key1, key2, value);
|
||||||
(member, key1, key2 = null, mode = "text", id = null) =>
|
|
||||||
(event) => {
|
|
||||||
const value = parseValue(event, mode, member, key1, key2, id);
|
|
||||||
|
|
||||||
const updatedMember = replaceValue({ ...member }, key1, key2, value);
|
const index = members.findIndex((item) => {
|
||||||
|
return item["config"]["id"] === member["config"]["id"];
|
||||||
|
});
|
||||||
|
let mutableMembers = [...members];
|
||||||
|
mutableMembers[index] = updatedMember;
|
||||||
|
setMembers(mutableMembers);
|
||||||
|
|
||||||
const index = members.findIndex((item) => {
|
const data = setValue({}, key1, key2, value);
|
||||||
return item["config"]["id"] === member["config"]["id"];
|
sendReq(member["config"]["id"], data);
|
||||||
});
|
};
|
||||||
let mutableMembers = [...members];
|
|
||||||
mutableMembers[index] = updatedMember;
|
|
||||||
setMembers(mutableMembers);
|
|
||||||
|
|
||||||
const data = setValue({}, key1, key2, value);
|
|
||||||
sendReq(member["config"]["id"], data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
id: "auth",
|
id: "auth",
|
||||||
name: t("authorized"),
|
name: "Authorized",
|
||||||
minWidth: "80px",
|
minWidth: "80px",
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -83,7 +86,7 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "address",
|
id: "address",
|
||||||
name: t("address"),
|
name: "Address",
|
||||||
minWidth: "150px",
|
minWidth: "150px",
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
<Typography variant="body2">{row.config.address}</Typography>
|
<Typography variant="body2">{row.config.address}</Typography>
|
||||||
|
@ -91,72 +94,44 @@ function NetworkMembers({ network }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "name",
|
id: "name",
|
||||||
name: t("name") + "/" + t("description"),
|
name: "Name/Description",
|
||||||
minWidth: "250px",
|
minWidth: "250px",
|
||||||
cell: (row) => <MemberName member={row} handleChange={handleChange} />,
|
cell: (row) => <MemberName member={row} handleChange={handleChange} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "ips",
|
id: "ips",
|
||||||
name: t("managedIPs"),
|
name: "Managed IPs",
|
||||||
minWidth: "220px",
|
minWidth: "220px",
|
||||||
cell: (row) => <ManagedIP member={row} handleChange={handleChange} />,
|
cell: (row) => <ManagedIP member={row} handleChange={handleChange} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "lastSeen",
|
***REMOVED***
|
||||||
name: t("lastSeen"),
|
id: "status",
|
||||||
|
name: "Peer status",
|
||||||
minWidth: "100px",
|
minWidth: "100px",
|
||||||
cell: (row) =>
|
cell: (row) =>
|
||||||
row.online === 1 ? (
|
row.online ? (
|
||||||
<Typography style={{ color: "#008000" }}>{"ONLINE"}</Typography>
|
<Typography style={{ color: "#008000" }}>
|
||||||
) : row.controllerId === row.config.address ? (
|
{"ONLINE (v" +
|
||||||
<Typography style={{ color: "#c5e31e" }}>{"CONTROLLER"}</Typography>
|
|
||||||
) : row.online === 0 ? (
|
|
||||||
<Typography color="error">
|
|
||||||
{row.lastOnline !== 0
|
|
||||||
? formatDistance(row.lastOnline, row.clock, {
|
|
||||||
includeSeconds: false,
|
|
||||||
addSuffix: true,
|
|
||||||
})
|
|
||||||
: "OFFLINE"}
|
|
||||||
</Typography>
|
|
||||||
) : (
|
|
||||||
<Typography style={{ color: "#f1c232" }}>{"RELAYED"}</Typography>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "physicalip",
|
|
||||||
name: t("version") + " / " + t("physIp") + " / " + t("latency"),
|
|
||||||
minWidth: "220px",
|
|
||||||
cell: (row) =>
|
|
||||||
row.online === 1 ? (
|
|
||||||
<p>
|
|
||||||
{"v" +
|
|
||||||
row.config.vMajor +
|
row.config.vMajor +
|
||||||
"." +
|
"." +
|
||||||
row.config.vMinor +
|
row.config.vMinor +
|
||||||
"." +
|
"." +
|
||||||
row.config.vRev}
|
row.config.vRev +
|
||||||
<br />
|
")"}
|
||||||
{row.physicalAddress + "/" + row.physicalPort}
|
</Typography>
|
||||||
<br />
|
|
||||||
{"(" + row.latency + " ms)"}
|
|
||||||
</p>
|
|
||||||
) : (
|
) : (
|
||||||
""
|
<Typography color="error">OFFLINE</Typography>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "delete",
|
id: "delete",
|
||||||
name: t("settings"),
|
name: "",
|
||||||
minWidth: "50px",
|
minWidth: "50px",
|
||||||
right: true,
|
right: true,
|
||||||
cell: (row) => (
|
cell: (row) => (
|
||||||
<>
|
<>
|
||||||
<MemberSettings
|
<MemberSettings member={row} handleChange={handleChange} />
|
||||||
member={row}
|
|
||||||
network={network}
|
|
||||||
handleChange={handleChange}
|
|
||||||
/>
|
|
||||||
<DeleteMember nwid={nwid} mid={row.config.id} callback={fetchData} />
|
<DeleteMember nwid={nwid} mid={row.config.id} callback={fetchData} />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
@ -166,7 +141,7 @@ function NetworkMembers({ network }) {
|
||||||
return (
|
return (
|
||||||
<Accordion defaultExpanded={true}>
|
<Accordion defaultExpanded={true}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>{t("member", { count: members.length })}</Typography>
|
<Typography>Members</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Grid container direction="column" spacing={3}>
|
<Grid container direction="column" spacing={3}>
|
||||||
|
@ -186,13 +161,14 @@ function NetworkMembers({ network }) {
|
||||||
spacing={0}
|
spacing={0}
|
||||||
direction="column"
|
direction="column"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="center"
|
justify="center"
|
||||||
style={{
|
style={{
|
||||||
minHeight: "50vh",
|
minHeight: "50vh",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h6" style={{ padding: "10%" }}>
|
<Typography variant="h6" style={{ padding: "10%" }}>
|
||||||
{t("noDevices")} <b>{nwid}</b>.
|
No devices have joined this network. Use the app on your
|
||||||
|
devices to join <b>{nwid}</b>.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import AddIcon from "@material-ui/icons/Add";
|
||||||
|
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function AddMember({ nwid, callback }) {
|
function AddMember({ nwid, callback }) {
|
||||||
const [member, setMember] = useState("");
|
const [member, setMember] = useState("");
|
||||||
|
|
||||||
|
@ -26,11 +24,9 @@ function AddMember({ nwid, callback }) {
|
||||||
setMember("");
|
setMember("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>{t("addMemberManually")}</Typography>
|
<Typography>Manually Add Member</Typography>
|
||||||
<List
|
<List
|
||||||
disablePadding={true}
|
disablePadding={true}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -12,10 +12,8 @@ import {
|
||||||
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
|
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
|
||||||
|
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function DeleteMember({ nwid, mid, callback }) {
|
function DeleteMember({ nwid, mid, callback }) {
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
|
@ -39,16 +37,18 @@ function DeleteMember({ nwid, mid, callback }) {
|
||||||
<DeleteOutlineIcon color="secondary" style={{ fontSize: 20 }} />
|
<DeleteOutlineIcon color="secondary" style={{ fontSize: 20 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Dialog open={open} onClose={handleClose}>
|
<Dialog open={open} onClose={handleClose}>
|
||||||
<DialogTitle>{t("deleteMemberConfirm")}</DialogTitle>
|
<DialogTitle>
|
||||||
|
{"Are you sure you want to delete this member?"}
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>{t("deleteAlert")}</DialogContentText>
|
<DialogContentText>This action cannot be undone.</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color="primary">
|
<Button onClick={handleClose} color="primary">
|
||||||
{t("cancel")}
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={deleteMemberReq} color="secondary">
|
<Button onClick={deleteMemberReq} color="secondary">
|
||||||
{t("delete")}
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import { Grid, TextField } from "@material-ui/core";
|
import { Grid, TextField } from "@material-ui/core";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function MemberName({ member, handleChange }) {
|
function MemberName({ member, handleChange }) {
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
return (
|
return (
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextField
|
<TextField
|
||||||
value={member.name}
|
value={member.name}
|
||||||
onChange={handleChange(member, "name")}
|
onChange={handleChange(member, "name")}
|
||||||
label={t("name")}
|
label="Name"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -17,7 +15,7 @@ function MemberName({ member, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={member.description}
|
value={member.description}
|
||||||
onChange={handleChange(member, "description")}
|
onChange={handleChange(member, "description")}
|
||||||
label={t("description")}
|
label="Description"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
|
|
@ -1,22 +1,16 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
FormControlLabel,
|
DialogContent,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
Paper,
|
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import BuildIcon from "@material-ui/icons/Build";
|
import BuildIcon from "@material-ui/icons/Build";
|
||||||
import { useState } from "react";
|
|
||||||
import Tag from "./components/Tag";
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
function MemberSettings({ member, handleChange }) {
|
||||||
|
|
||||||
function MemberSettings({ member, network, handleChange }) {
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = () => {
|
||||||
|
@ -33,9 +27,7 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
<BuildIcon style={{ fontSize: 20 }} />
|
<BuildIcon style={{ fontSize: 20 }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Dialog open={open} onClose={handleClose}>
|
<Dialog open={open} onClose={handleClose}>
|
||||||
<DialogTitle>
|
<DialogTitle>{"Member " + member.config.id + " settings"}</DialogTitle>
|
||||||
{t("member") + member.config.id + t("settings")}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -48,7 +40,7 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
"checkbox"
|
"checkbox"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span>{t("allowBridging")}</span>
|
<span>Allow Ethernet Bridging</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -61,67 +53,7 @@ function MemberSettings({ member, network, handleChange }) {
|
||||||
"checkbox"
|
"checkbox"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span>{t("noAutoIP")}</span>
|
<span>Do Not Auto-Assign IPs</span>
|
||||||
</Grid>
|
|
||||||
<Grid container spacing={2}>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Typography variant="h6">{t("capabilities")}</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Paper style={{ padding: 20 }}>
|
|
||||||
{Object.entries(network["capabilitiesByName"] || []).length ===
|
|
||||||
0
|
|
||||||
? t("noCapDef")
|
|
||||||
: ""}
|
|
||||||
{Object.entries(network["capabilitiesByName"] || []).map(
|
|
||||||
([capName, capId]) => (
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={member["config"]["capabilities"].includes(
|
|
||||||
capId
|
|
||||||
)}
|
|
||||||
color="primary"
|
|
||||||
onChange={handleChange(
|
|
||||||
member,
|
|
||||||
"config",
|
|
||||||
"capabilities",
|
|
||||||
"capChange",
|
|
||||||
capId
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
key={"cap-" + capId}
|
|
||||||
label={capName}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Grid container spacing={2}>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Typography variant="h6">{t("tags")}</Typography>
|
|
||||||
</Grid>
|
|
||||||
{Object.entries(network["tagsByName"] || []).length === 0 ? (
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Paper style={{ padding: 20 }}>{t("noTagDef")}</Paper>
|
|
||||||
</Grid>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{Object.entries(network["tagsByName"] || []).map(
|
|
||||||
([tagName, tagDetail]) => (
|
|
||||||
<Grid item xs={12} sm={6} key={"tag-" + tagName}>
|
|
||||||
<Tag
|
|
||||||
member={member}
|
|
||||||
tagName={tagName}
|
|
||||||
tagDetail={tagDetail}
|
|
||||||
handleChange={handleChange}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
@ -1,169 +0,0 @@
|
||||||
import {
|
|
||||||
Checkbox,
|
|
||||||
FormControlLabel,
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
Input,
|
|
||||||
Paper,
|
|
||||||
Select,
|
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useDebounce } from "react-use";
|
|
||||||
|
|
||||||
function Tag({ member, tagName, tagDetail, handleChange }) {
|
|
||||||
const [tagValue, setTagValue] = useState("");
|
|
||||||
const [tagChangedByUser, setTagChangedByUser] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let tagIndex = member["config"]["tags"].findIndex((item) => {
|
|
||||||
return item[0] === tagDetail["id"];
|
|
||||||
});
|
|
||||||
let value = "";
|
|
||||||
if (tagIndex !== -1) {
|
|
||||||
value = member["config"]["tags"][tagIndex][1];
|
|
||||||
}
|
|
||||||
value = value !== false ? value : "";
|
|
||||||
setTagValue(value);
|
|
||||||
}, [member, tagDetail]);
|
|
||||||
|
|
||||||
useDebounce(
|
|
||||||
async () => {
|
|
||||||
if (tagChangedByUser) {
|
|
||||||
let value = tagValue === "" ? "" : parseInt(tagValue);
|
|
||||||
let event = { target: { value: value } };
|
|
||||||
handleChange(
|
|
||||||
member,
|
|
||||||
"config",
|
|
||||||
"tags",
|
|
||||||
"tagChange",
|
|
||||||
tagDetail["id"]
|
|
||||||
)(event);
|
|
||||||
}
|
|
||||||
setTagChangedByUser(false);
|
|
||||||
},
|
|
||||||
500,
|
|
||||||
[tagValue]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSelectChange = (event) => {
|
|
||||||
let newValue = event.target.value;
|
|
||||||
setTagChangedByUser(true);
|
|
||||||
setTagValue(newValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFlagChange = (value) => (event) => {
|
|
||||||
let newValue;
|
|
||||||
let oldValue;
|
|
||||||
|
|
||||||
if (tagValue === "") {
|
|
||||||
oldValue = 0;
|
|
||||||
} else {
|
|
||||||
oldValue = tagValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.target.checked) {
|
|
||||||
newValue = oldValue + value;
|
|
||||||
} else {
|
|
||||||
newValue = oldValue - value;
|
|
||||||
}
|
|
||||||
setTagChangedByUser(true);
|
|
||||||
setTagValue(newValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (event) => {
|
|
||||||
let value = event.target.value;
|
|
||||||
if (/^(|0|[1-9]\d*)$/.test(value)) {
|
|
||||||
value = value === "" ? value : parseInt(value);
|
|
||||||
} else {
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
setTagChangedByUser(true);
|
|
||||||
setTagValue(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearTag = (event) => {
|
|
||||||
setTagChangedByUser(true);
|
|
||||||
setTagValue("");
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper style={{ padding: 20 }}>
|
|
||||||
<Grid container spacing={2}>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Typography
|
|
||||||
variant="h6"
|
|
||||||
color={tagValue === "" ? "error" : "primary"}
|
|
||||||
>
|
|
||||||
{tagName}
|
|
||||||
{tagValue === "" ? (
|
|
||||||
""
|
|
||||||
) : (
|
|
||||||
<IconButton aria-label="delete" onClick={clearTag}>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid container>
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<Select
|
|
||||||
native
|
|
||||||
value={tagValue}
|
|
||||||
onChange={handleSelectChange}
|
|
||||||
displayEmpty
|
|
||||||
style={{ minWidth: 100 }}
|
|
||||||
>
|
|
||||||
<option value="">--</option>
|
|
||||||
{Object.entries(tagDetail["enums"]).map(
|
|
||||||
([enumKey, enumValue]) => (
|
|
||||||
<option key={enumKey} value={enumValue}>
|
|
||||||
{enumKey}
|
|
||||||
</option>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
{Object.values(tagDetail["enums"]).length === 0 &&
|
|
||||||
tagValue !== "" ? (
|
|
||||||
<option value={tagValue}>(no enums)</option>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
{Object.values(tagDetail["enums"]).length !== 0 &&
|
|
||||||
!Object.values(tagDetail["enums"]).includes(tagValue) &&
|
|
||||||
tagValue !== "" ? (
|
|
||||||
<option value={tagValue}>(custom)</option>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<Input
|
|
||||||
value={tagValue}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
placeholder="Tag Value"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
{Object.entries(tagDetail["flags"]).map(([flagKey, flagValue]) => (
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={(tagValue & flagValue) === flagValue}
|
|
||||||
onChange={handleFlagChange(flagValue)}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
key={"flag-" + flagKey}
|
|
||||||
label={flagKey}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Tag;
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from "./Tag";
|
|
|
@ -1,37 +1,34 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionDetails,
|
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Grid,
|
|
||||||
Hidden,
|
|
||||||
Snackbar,
|
Snackbar,
|
||||||
|
Hidden,
|
||||||
|
Grid,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||||
|
|
||||||
import CodeMirror from "@uiw/react-codemirror";
|
import CodeMirror from "@uiw/react-codemirror";
|
||||||
import "codemirror/theme/3024-day.css";
|
import "codemirror/theme/3024-day.css";
|
||||||
|
|
||||||
import { compile } from "external/RuleCompiler";
|
import { compile } from "external/RuleCompiler";
|
||||||
|
|
||||||
import debounce from "lodash/debounce";
|
import debounce from "lodash/debounce";
|
||||||
import { useState } from "react";
|
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
function NetworkRules({ network }) {
|
||||||
|
|
||||||
function NetworkRules({ network, callback }) {
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
const [editor, setEditor] = useState(null);
|
const [editor, setEditor] = useState(null);
|
||||||
const [flowData, setFlowData] = useState({
|
const [flowData, setFlowData] = useState({
|
||||||
rules: [...network.config.rules],
|
rules: [...network.config.rules],
|
||||||
capabilities: [...network.config.capabilities],
|
capabilities: [...network.config.capabilities],
|
||||||
tags: [...network.config.tags],
|
tags: [...network.config.tags],
|
||||||
});
|
});
|
||||||
const [tagCapByNameData, setTagCapByNameData] = useState({
|
|
||||||
tagsByName: network.tagsByName || {},
|
|
||||||
capabilitiesByName: network.capabilitiesByName || {},
|
|
||||||
});
|
|
||||||
const [errors, setErrors] = useState([]);
|
const [errors, setErrors] = useState([]);
|
||||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||||
|
|
||||||
|
@ -40,16 +37,12 @@ function NetworkRules({ network, callback }) {
|
||||||
const req = await API.post("/network/" + network["config"]["id"], {
|
const req = await API.post("/network/" + network["config"]["id"], {
|
||||||
config: { ...flowData },
|
config: { ...flowData },
|
||||||
rulesSource: editor.getValue(),
|
rulesSource: editor.getValue(),
|
||||||
...tagCapByNameData,
|
|
||||||
});
|
});
|
||||||
console.log("Action", req);
|
console.log("Action", req);
|
||||||
setSnackbarOpen(true);
|
setSnackbarOpen(true);
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setSnackbarOpen(false);
|
setSnackbarOpen(false);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
||||||
callback();
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -58,29 +51,14 @@ function NetworkRules({ network, callback }) {
|
||||||
const src = event.getValue();
|
const src = event.getValue();
|
||||||
setEditor(event);
|
setEditor(event);
|
||||||
let rules = [],
|
let rules = [],
|
||||||
caps = {},
|
caps = [],
|
||||||
tags = {};
|
tags = [];
|
||||||
const res = compile(src, rules, caps, tags);
|
const res = compile(src, rules, caps, tags);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
let capabilitiesByName = {};
|
|
||||||
for (var key in caps) {
|
|
||||||
capabilitiesByName[key] = caps[key].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tagsByName = { ...tags };
|
|
||||||
for (let key in tags) {
|
|
||||||
tags[key] = { id: tags[key].id, default: tags[key].default };
|
|
||||||
}
|
|
||||||
|
|
||||||
setFlowData({
|
setFlowData({
|
||||||
rules: [...rules],
|
rules: [...rules],
|
||||||
capabilities: [...Object.values(caps)],
|
capabilities: [...caps],
|
||||||
tags: [...Object.values(tags)],
|
tags: [...tags],
|
||||||
});
|
|
||||||
|
|
||||||
setTagCapByNameData({
|
|
||||||
tagsByName: tagsByName,
|
|
||||||
capabilitiesByName: capabilitiesByName,
|
|
||||||
});
|
});
|
||||||
setErrors([]);
|
setErrors([]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,12 +69,12 @@ function NetworkRules({ network, callback }) {
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>{t("flowRules")}</Typography>
|
<Typography>Flow Rules</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
{/* Important note: value in CodeMirror instance means INITAIL VALUE
|
{/* Important note: value in CodeMirror instance means INITAIL VALUE
|
||||||
or it could be used to replace editor state with the new value.
|
or it could be used to replace editor state with the new value.
|
||||||
No need to update on every user character input Flow Rules
|
No need to update on every user character input
|
||||||
*/}
|
*/}
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
value={network["rulesSource"]}
|
value={network["rulesSource"]}
|
||||||
|
@ -128,13 +106,13 @@ function NetworkRules({ network, callback }) {
|
||||||
width: "250px",
|
width: "250px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{errors.length ? (
|
{!!errors.length ? (
|
||||||
<Typography color="error">
|
<Typography color="error">
|
||||||
{"[" + errors[0] + ":" + errors[1] + "] " + errors[2]}
|
{"[" + errors[0] + ":" + errors[1] + "] " + errors[2]}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="contained" color="primary" onClick={saveChanges}>
|
<Button variant="contained" color="primary" onClick={saveChanges}>
|
||||||
{t("saveChanges")}
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -17,10 +17,7 @@ import IPv4AutoAssign from "./components/IPv4AutoAssign";
|
||||||
import API from "utils/API";
|
import API from "utils/API";
|
||||||
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
|
import { parseValue, replaceValue, setValue } from "utils/ChangeHelper";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function NetworkSettings({ network, setNetwork }) {
|
function NetworkSettings({ network, setNetwork }) {
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const sendReq = async (data) => {
|
const sendReq = async (data) => {
|
||||||
try {
|
try {
|
||||||
const req = await API.post("/network/" + network["config"]["id"], data);
|
const req = await API.post("/network/" + network["config"]["id"], data);
|
||||||
|
@ -30,28 +27,28 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange =
|
const handleChange = (key1, key2, mode = "text", additionalData = null) => (
|
||||||
(key1, key2, mode = "text", additionalData = null) =>
|
event
|
||||||
(event) => {
|
) => {
|
||||||
const value = parseValue(event, mode, additionalData);
|
const value = parseValue(event, mode, additionalData);
|
||||||
|
|
||||||
let updatedNetwork = replaceValue({ ...network }, key1, key2, value);
|
let updatedNetwork = replaceValue({ ...network }, key1, key2, value);
|
||||||
setNetwork(updatedNetwork);
|
setNetwork(updatedNetwork);
|
||||||
|
|
||||||
let data = setValue({}, key1, key2, value);
|
let data = setValue({}, key1, key2, value);
|
||||||
|
|
||||||
sendReq(data);
|
sendReq(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography>{t("generalSettings")}</Typography>
|
<Typography>General settings</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Grid container direction="column" spacing={3}>
|
<Grid container direction="column" spacing={3}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography>{t("networkId")}</Typography>
|
<Typography>Network ID</Typography>
|
||||||
<Typography variant="h5">
|
<Typography variant="h5">
|
||||||
<span>{network["config"]["id"]}</span>
|
<span>{network["config"]["id"]}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -60,7 +57,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={network["config"]["name"]}
|
value={network["config"]["name"]}
|
||||||
onChange={handleChange("config", "name")}
|
onChange={handleChange("config", "name")}
|
||||||
label={t("name")}
|
label="Name"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -72,9 +69,9 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
value={network["description"]}
|
value={network["description"]}
|
||||||
onChange={handleChange("description")}
|
onChange={handleChange("description")}
|
||||||
multiline
|
multiline
|
||||||
minRows={2}
|
rows={2}
|
||||||
maxRows={Infinity}
|
rowsMax={Infinity}
|
||||||
label={t("description")}
|
label="Description"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
InputLabelProps={{
|
InputLabelProps={{
|
||||||
shrink: true,
|
shrink: true,
|
||||||
|
@ -83,14 +80,14 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography>{t("accessControl")}</Typography>
|
<Typography>Access Control</Typography>
|
||||||
<Select
|
<Select
|
||||||
native
|
native
|
||||||
value={network["config"]["private"]}
|
value={network["config"]["private"]}
|
||||||
onChange={handleChange("config", "private", "json")}
|
onChange={handleChange("config", "private", "json")}
|
||||||
>
|
>
|
||||||
<option value={1}>{t("private")}</option>
|
<option value={true}>Private</option>
|
||||||
<option value={0}>{t("public")}</option>
|
<option value={false}>Public</option>
|
||||||
</Select>
|
</Select>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
@ -114,7 +111,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
<Divider />
|
<Divider />
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<TextField
|
<TextField
|
||||||
label={t("multicastLimit")}
|
label="Multicast Recipient Limit"
|
||||||
type="number"
|
type="number"
|
||||||
value={network["config"]["multicastLimit"]}
|
value={network["config"]["multicastLimit"]}
|
||||||
onChange={handleChange("config", "multicastLimit", "json")}
|
onChange={handleChange("config", "multicastLimit", "json")}
|
||||||
|
@ -129,7 +126,7 @@ function NetworkSettings({ network, setNetwork }) {
|
||||||
color="primary"
|
color="primary"
|
||||||
onChange={handleChange("config", "enableBroadcast", "checkbox")}
|
onChange={handleChange("config", "enableBroadcast", "checkbox")}
|
||||||
/>
|
/>
|
||||||
<span>{t("enableBroadcast")}</span>
|
<span>Enable Broadcast</span>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* TODO: */}
|
{/* TODO: */}
|
||||||
{/* <Grid item>
|
{/* <Grid item>
|
||||||
|
|
|
@ -18,10 +18,7 @@ import DataTable from "react-data-table-component";
|
||||||
import { addressPool } from "utils/NetworkConfig";
|
import { addressPool } from "utils/NetworkConfig";
|
||||||
import { getCIDRAddress, validateIP, normilizeIP } from "utils/IP";
|
import { getCIDRAddress, validateIP, normilizeIP } from "utils/IP";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const [start, setStart] = useState("");
|
const [start, setStart] = useState("");
|
||||||
const [end, setEnd] = useState("");
|
const [end, setEnd] = useState("");
|
||||||
|
|
||||||
|
@ -92,19 +89,19 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Start",
|
id: "Start",
|
||||||
name: t("start"),
|
name: "Start",
|
||||||
cell: (row) => row["ipRangeStart"],
|
cell: (row) => row["ipRangeStart"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "End",
|
id: "End",
|
||||||
name: t("end"),
|
name: "End",
|
||||||
cell: (row) => row["ipRangeEnd"],
|
cell: (row) => row["ipRangeEnd"],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>{t("ipv4AutoAssign")}</Typography>
|
<Typography>IPv4 Auto-Assign</Typography>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "30px",
|
padding: "30px",
|
||||||
|
@ -125,7 +122,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
<Typography style={{ paddingBottom: "10px" }}>
|
<Typography style={{ paddingBottom: "10px" }}>
|
||||||
{t("autoAssignPool")}
|
Auto-Assign Pools
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box border={1} borderColor="grey.300">
|
<Box border={1} borderColor="grey.300">
|
||||||
<Grid item style={{ margin: "10px" }}>
|
<Grid item style={{ margin: "10px" }}>
|
||||||
|
@ -135,7 +132,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
data={ipAssignmentPools}
|
data={ipAssignmentPools}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Typography>{t("addIPv4Pool")}</Typography>
|
<Typography>Add IPv4 Pool</Typography>
|
||||||
<List
|
<List
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -145,7 +142,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={start}
|
value={start}
|
||||||
onChange={handleStartInput}
|
onChange={handleStartInput}
|
||||||
placeholder={t("start")}
|
placeholder={"Start"}
|
||||||
/>
|
/>
|
||||||
<Divider
|
<Divider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
|
@ -157,7 +154,7 @@ function IPv4AutoAssign({ ipAssignmentPools, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={end}
|
value={end}
|
||||||
onChange={handleEndInput}
|
onChange={handleEndInput}
|
||||||
placeholder={t("end")}
|
placeholder={"End"}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
@ -16,10 +16,7 @@ import DataTable from "react-data-table-component";
|
||||||
|
|
||||||
import { validateIP, normilizeIP, validateCIDR } from "utils/IP";
|
import { validateIP, normilizeIP, validateCIDR } from "utils/IP";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function ManagedRoutes({ routes, handleChange }) {
|
function ManagedRoutes({ routes, handleChange }) {
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const [destination, setDestination] = useState("");
|
const [destination, setDestination] = useState("");
|
||||||
const [via, setVia] = useState("");
|
const [via, setVia] = useState("");
|
||||||
|
|
||||||
|
@ -74,12 +71,12 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "target",
|
id: "target",
|
||||||
name: t("target"),
|
name: "Target",
|
||||||
cell: (row) => row["target"],
|
cell: (row) => row["target"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "via",
|
id: "via",
|
||||||
name: t("via"),
|
name: "via",
|
||||||
cell: (row) => (row["via"] ? row["via"] : "(LAN)"),
|
cell: (row) => (row["via"] ? row["via"] : "(LAN)"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -87,13 +84,13 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography style={{ paddingBottom: "10px" }}>
|
<Typography style={{ paddingBottom: "10px" }}>
|
||||||
{t("managedRoutes")} ({routes.length + "/128"})
|
Managed Routes ({routes.length + "/32"})
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box border={1} borderColor="grey.300">
|
<Box border={1} borderColor="grey.300">
|
||||||
<Grid item style={{ margin: "10px" }}>
|
<Grid item style={{ margin: "10px" }}>
|
||||||
<DataTable noHeader={true} columns={columns} data={routes} />
|
<DataTable noHeader={true} columns={columns} data={routes} />
|
||||||
<Divider />
|
<Divider />
|
||||||
<Typography>{t("addRoute")}</Typography>
|
<Typography>Add Routes</Typography>
|
||||||
<List
|
<List
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -103,7 +100,7 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={destination}
|
value={destination}
|
||||||
onChange={handleDestinationInput}
|
onChange={handleDestinationInput}
|
||||||
placeholder={t("destination") + " (CIDR)"}
|
placeholder={"Destination (CIDR)"}
|
||||||
/>
|
/>
|
||||||
<Divider
|
<Divider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
|
@ -115,7 +112,7 @@ function ManagedRoutes({ routes, handleChange }) {
|
||||||
<TextField
|
<TextField
|
||||||
value={via}
|
value={via}
|
||||||
onChange={handleViaInput}
|
onChange={handleViaInput}
|
||||||
placeholder={t("via") + " (" + t("optional") + ")"}
|
placeholder={"Via (Optional)"}
|
||||||
/>
|
/>
|
||||||
<IconButton size="small" color="primary" onClick={addRouteReq}>
|
<IconButton size="small" color="primary" onClick={addRouteReq}>
|
||||||
<AddIcon
|
<AddIcon
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionSummary,
|
|
||||||
AccordionDetails,
|
|
||||||
Grid,
|
|
||||||
Typography,
|
|
||||||
Select,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import localesList from "generated/localesList.json";
|
|
||||||
|
|
||||||
function Settings() {
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
const handleChange = () => (event) => {
|
|
||||||
i18n.changeLanguage(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Accordion>
|
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
|
||||||
<Typography>{t("language")}</Typography>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails>
|
|
||||||
<Grid item>
|
|
||||||
<Select native value={i18n.language} onChange={handleChange()}>
|
|
||||||
{localesList.map((locale) => (
|
|
||||||
<option key={locale.code} value={locale.code}>
|
|
||||||
{locale.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Grid>
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Settings;
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from "./Settings";
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { ThemeProvider } from "@material-ui/styles";
|
import { ThemeProvider } from "@material-ui/styles";
|
||||||
import { createTheme } from "@material-ui/core/styles";
|
import { createMuiTheme } from "@material-ui/core/styles";
|
||||||
import { red, amber } from "@material-ui/core/colors";
|
import { red, amber } from "@material-ui/core/colors";
|
||||||
|
|
||||||
const theme = createTheme({
|
const theme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: amber[500],
|
main: amber[500],
|
||||||
|
|
1
frontend/src/external/RuleCompiler.js
vendored
1
frontend/src/external/RuleCompiler.js
vendored
|
@ -1,4 +1,3 @@
|
||||||
// @ts-nocheck
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// rule-compiler.js from ZeroTierOne before its relicensing
|
// rule-compiler.js from ZeroTierOne before its relicensing
|
||||||
// source:
|
// source:
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"code": "en",
|
|
||||||
"name": "English"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "es-ES",
|
|
||||||
"name": "Español"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ru-RU",
|
|
||||||
"name": "Русский"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "zh_CN",
|
|
||||||
"name": "中文"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,35 +0,0 @@
|
||||||
import i18n from "i18next";
|
|
||||||
import languageDetector from "i18next-browser-languagedetector";
|
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
import Backend from "i18next-http-backend";
|
|
||||||
|
|
||||||
import localesList from "./utils/localesList.json";
|
|
||||||
const supportedLngs = localesList.map((locale) => locale.code);
|
|
||||||
|
|
||||||
i18n
|
|
||||||
.use(languageDetector)
|
|
||||||
.use(initReactI18next)
|
|
||||||
.use(Backend)
|
|
||||||
.init({
|
|
||||||
compatibilityJSON: "v4",
|
|
||||||
fallbackLng: "en",
|
|
||||||
detection: {
|
|
||||||
order: ["path", "cookie", "localStorage", "htmlTag"],
|
|
||||||
caches: ["localStorage", "cookie"],
|
|
||||||
},
|
|
||||||
debug: true,
|
|
||||||
interpolation: {
|
|
||||||
escapeValue: true,
|
|
||||||
},
|
|
||||||
react: {
|
|
||||||
useSuspense: true,
|
|
||||||
},
|
|
||||||
supportedLngs,
|
|
||||||
backend: {
|
|
||||||
loadPath: "/locales/{{lng}}/{{ns}}.json",
|
|
||||||
},
|
|
||||||
ns: ["common"],
|
|
||||||
defaultNS: "common",
|
|
||||||
});
|
|
||||||
|
|
||||||
export default i18n;
|
|
|
@ -1,17 +1,8 @@
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
font-family:
|
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Oxygen',
|
||||||
"Roboto",
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
"Segoe UI",
|
|
||||||
"Oxygen",
|
|
||||||
"Ubuntu",
|
|
||||||
"Cantarell",
|
|
||||||
"Fira Sans",
|
|
||||||
"Droid Sans",
|
|
||||||
"Helvetica Neue",
|
|
||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { Grid, Link, Typography } from "@material-ui/core";
|
import { useState, useEffect } from "react";
|
||||||
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
|
import { Link as RouterLink, useParams, useHistory } from "react-router-dom";
|
||||||
import NetworkHeader from "components/NetworkHeader";
|
|
||||||
import NetworkManagement from "components/NetworkManagement";
|
|
||||||
import NetworkMembers from "components/NetworkMembers";
|
|
||||||
import NetworkRules from "components/NetworkRules";
|
|
||||||
import NetworkSettings from "components/NetworkSettings";
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import { Link as RouterLink, useHistory, useParams } from "react-router-dom";
|
|
||||||
import { useLocalStorage } from "react-use";
|
import { useLocalStorage } from "react-use";
|
||||||
import API from "utils/API";
|
|
||||||
|
import { Link, Grid, Typography } from "@material-ui/core";
|
||||||
|
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
|
||||||
import useStyles from "./Network.styles";
|
import useStyles from "./Network.styles";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import NetworkHeader from "components/NetworkHeader";
|
||||||
|
import NetworkSettings from "components/NetworkSettings";
|
||||||
|
import NetworkMembers from "components/NetworkMembers";
|
||||||
|
import NetworkRules from "components/NetworkRules";
|
||||||
|
import NetworkManagment from "components/NetworkManagment";
|
||||||
|
|
||||||
|
import API from "utils/API";
|
||||||
|
|
||||||
function Network() {
|
function Network() {
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const { nwid } = useParams();
|
const { nwid } = useParams();
|
||||||
const [loggedIn] = useLocalStorage("loggedIn", false);
|
const [loggedIn] = useLocalStorage("loggedIn", false);
|
||||||
const [network, setNetwork] = useState({});
|
const [network, setNetwork] = useState({});
|
||||||
|
@ -22,32 +22,29 @@ function Network() {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const network = await API.get("network/" + nwid);
|
|
||||||
setNetwork(network.data);
|
|
||||||
console.log("Current network:", network.data);
|
|
||||||
} catch (err) {
|
|
||||||
if (err.response.status === 404) {
|
|
||||||
history.push("/404");
|
|
||||||
}
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}, [nwid, history]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
const network = await API.get("network/" + nwid);
|
||||||
|
setNetwork(network.data);
|
||||||
|
console.log("Current network:", network.data);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response.status === 404) {
|
||||||
|
history.push("/404");
|
||||||
|
}
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [nwid, fetchData]);
|
}, [nwid, history]);
|
||||||
|
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.breadcrumbs}>
|
<Link color="inherit" component={RouterLink} to="/" underline="none">
|
||||||
<Link color="inherit" component={RouterLink} to="/" underline="none">
|
<ArrowBackIcon className={classes.backIcon}></ArrowBackIcon>
|
||||||
<ArrowBackIcon className={classes.backIcon}></ArrowBackIcon>
|
Networks
|
||||||
{t("network", { count: 2 })}
|
</Link>
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
{network["config"] && (
|
{network["config"] && (
|
||||||
<>
|
<>
|
||||||
|
@ -55,11 +52,9 @@ function Network() {
|
||||||
<NetworkSettings network={network} setNetwork={setNetwork} />
|
<NetworkSettings network={network} setNetwork={setNetwork} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<NetworkMembers network={network} />
|
<NetworkMembers />
|
||||||
{network["config"] && (
|
{network["config"] && <NetworkRules network={network} />}
|
||||||
<NetworkRules network={network} callback={fetchData} />
|
<NetworkManagment />
|
||||||
)}
|
|
||||||
<NetworkManagement />
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -76,7 +71,9 @@ function Network() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Grid item xs={10}>
|
<Grid item xs={10}>
|
||||||
<Typography variant="h5">{t("notAuthorized")}</Typography>
|
<Typography variant="h5">
|
||||||
|
You are not authorized. Please Log In
|
||||||
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,11 +5,7 @@ const useStyles = makeStyles((theme) => ({
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
margin: "3%",
|
margin: "1%",
|
||||||
},
|
|
||||||
breadcrumbs: {
|
|
||||||
paddingTop: "2%",
|
|
||||||
paddingLeft: "2%",
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
import { Grid, Link, Typography } from "@material-ui/core";
|
|
||||||
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
|
|
||||||
import SettingsComponent from "components/Settings";
|
|
||||||
|
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
|
||||||
import { useLocalStorage } from "react-use";
|
|
||||||
|
|
||||||
import useStyles from "./Settings.styles";
|
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
function Settings() {
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
const [loggedIn] = useLocalStorage("loggedIn", false);
|
|
||||||
|
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
if (loggedIn) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={classes.breadcrumbs}>
|
|
||||||
<Link color="inherit" component={RouterLink} to="/" underline="none">
|
|
||||||
<ArrowBackIcon className={classes.backIcon}></ArrowBackIcon>
|
|
||||||
{t("settings")}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className={classes.container}>
|
|
||||||
<SettingsComponent />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
spacing={0}
|
|
||||||
direction="column"
|
|
||||||
alignItems="center"
|
|
||||||
justify="center"
|
|
||||||
style={{
|
|
||||||
minHeight: "50vh",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid item xs={10}>
|
|
||||||
<Typography variant="h5">{t("notAuthorized")}</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Settings;
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
backIcon: {
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
margin: "3%",
|
|
||||||
},
|
|
||||||
breadcrumbs: {
|
|
||||||
paddingTop: "2%",
|
|
||||||
paddingLeft: "2%",
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default useStyles;
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from "./Settings";
|
|
1
frontend/src/types.d.ts
vendored
1
frontend/src/types.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
declare module "*.png";
|
|
|
@ -5,11 +5,7 @@ const baseURL = "/api/";
|
||||||
export default axios.create({
|
export default axios.create({
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
responseType: "json",
|
responseType: "json",
|
||||||
withCredentials: "true",
|
headers: {
|
||||||
headers:
|
Authorization: `Bearer ${JSON.parse(localStorage.getItem("token"))}`,
|
||||||
localStorage.getItem("disableAuth") === "true"
|
},
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
Authorization: `token ${JSON.parse(localStorage.getItem("token"))}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue