Compare commits

...

170 Commits

Author SHA1 Message Date
Philip H 13616a2f1e Security vulnerability patched and minor improvements (#1071, #1072) 2024-05-27 21:06:22 +02:00
Philip H 5e015bfdb5 Security vulnerability patched and minor improvements (#1071)
Thank you so much @davide-acanfora!
2024-05-27 20:41:46 +02:00
davide-acanfora d2d15fca2a Path traversal vulnerability resolved 2024-05-27 20:25:00 +02:00
davide-acanfora c26b536b65 Remove unnecessary bcryptjs module usage 2024-05-27 20:25:00 +02:00
davide-acanfora 859dd2f25b Replace uuid module with built in crypto for UUIDv4 generation 2024-05-27 20:25:00 +02:00
davide-acanfora e80ff54ebc Don't print release number to anyone who visits the service 2024-05-27 20:25:00 +02:00
NPM Update Bot 4bfef3c0c0 npm: package updates 2024-05-27 00:03:06 +00:00
NPM Update Bot 93d9f0b6fe npm: package updates 2024-05-24 19:35:49 +00:00
Philip H 54236eb8b4 Documentation: image tags and UI_CHART_TYPE (#1057) 2024-05-24 21:35:11 +02:00
Philip H 8249b92a34 fixup: add UI_CHART_TYPE docs 2024-05-24 21:32:17 +02:00
Philip H 4cd5d5459a Documentation: docker image versions 2024-05-23 11:32:13 +02:00
NPM Update Bot 678cf5bffb npm: package updates 2024-05-23 07:46:21 +00:00
Philip H ad80017846 Dockerfile: remove unused parts
We expose ports another way
2024-05-23 09:45:37 +02:00
Philip H 975c61df6d fixup: desktop example iOS -> macOS 2024-05-20 12:52:18 +02:00
Philip H 69726cba75 fixup: WG_PORT in config.js (#1056)
patch for v13
2024-05-18 14:15:19 +02:00
Philip H 211e0b6aa2 Update README.md 2024-05-18 14:13:03 +02:00
taohua 8df1b6ff54 Update config.js
should respect WG_PORT, not hard code 51820.
2024-05-18 14:13:03 +02:00
Philip H cf94b98482 fixup: Server.js for instance without docker (#1044)
patch for v13
2024-05-16 20:21:37 +02:00
NPM Update Bot 3844d04569 npm: package updates 2024-05-16 20:19:22 +02:00
Philip H 68c6f6252e fixup: Server.js for instance without docker
Some users can no longer access the Web UI since the release of v13.
2024-05-16 20:19:22 +02:00
Philip H 519f4efa20 Version 13: new framework, UI_CHART_TYPE, some bugfixes and more (#967)
Thanks to all contributors to get us able to release v13!
2024-05-13 20:44:16 +02:00
NPM Update Bot c6dd456a07 npm: package updates 2024-05-13 00:03:03 +00:00
NPM Update Bot a43d2201fc npm: package updates 2024-05-09 18:01:15 +00:00
Philip H 86146ccc68 Dockerfile: ensure to use latest npm cli 2024-05-09 20:00:36 +02:00
NPM Update Bot 191dd74b0c npm: package updates 2024-05-09 15:29:08 +00:00
Philip H e2eb7bc362 avoid warnings on ci
as we support nodejs latest lts version on instance without docker
2024-05-09 17:28:28 +02:00
NPM Update Bot b60461e917 npm: package updates 2024-05-09 11:21:47 +00:00
Philip H fb628bcb89 Update npm to patched version of latest nodejs lts 2024-05-09 13:21:09 +02:00
NPM Update Bot 195e307ff5 npm: package updates 2024-05-04 19:00:17 +00:00
Philip H e46efd6088 package.json: rollback nodejs 18
because of build hang on newer nodejs version stick to nodejs 18
2024-05-04 20:59:36 +02:00
NPM Update Bot 66bb13ed30 npm: package updates 2024-05-04 13:19:02 +00:00
Philip H. 34fdc313ae docker-compose: version is obsolete 2024-05-04 13:18:20 +00:00
NPM Update Bot 9b5b8c77c3 npm: package updates 2024-04-30 17:53:00 +00:00
Philip H 7d05a82dae Improved local development and Dockerfile (#1015)
Thanks @davide-acanfora
2024-04-30 19:52:20 +02:00
Philip H a25d28755c Improved local development and Dockerfile (#1014)
Thanks @davide-acanfora
2024-04-30 19:35:13 +02:00
davide-acanfora cada04ad0e Use the newer Docker Compose 2024-04-30 17:04:33 +02:00
davide-acanfora 6b2f57f2f1 Added nodemon as a dev dependency and removed unnecessary instructions from the Dockerfile 2024-04-30 17:03:48 +02:00
davide-acanfora 488e3c32b3 Fix typo in code comments 2024-04-30 17:00:53 +02:00
NPM Update Bot 4cc07c5312 npm: package updates 2024-04-29 00:03:04 +00:00
Philip H c5d328be2a fixup: Dockerfile 2024-04-27 18:55:06 +02:00
Philip H 155541fbdb fixup: add timeout otherwise it reports unhealthy 2024-04-27 18:44:05 +02:00
Philip H 3f763d2607 implement healthcheck into Dockerfile (#1009)
Thanks @CosasDePuma
2024-04-27 18:39:40 +02:00
Philip H f9656d0bfe healthcheck is integrated internally 2024-04-27 18:35:22 +02:00
Philip H 83408f6a9b add healthcheck into Dockerfile
Thanks @CosasDePuma
2024-04-27 18:34:20 +02:00
NPM Update Bot 4911082a34 npm: package updates 2024-04-27 16:19:23 +00:00
Philip H a3a69654fc docker-compose.yml: add healthcheck
Thanks @CosasDePuma
2024-04-27 18:18:51 +02:00
Philip H 4f8ee27d77 Create CODEOWNERS 2024-04-25 12:24:48 +02:00
NPM Update Bot 1e2da39a87 npm: package updates 2024-04-20 16:06:25 +00:00
Philip H e153998d29 Dockerfile: bump to nodejs 20 (#999) 2024-04-20 18:05:41 +02:00
Philip H 6b284cb27c Dockerfile: add note 2024-04-19 17:12:07 +02:00
Philip H e6db5e8b7f Dockerfile: missing operator 2024-04-19 17:12:07 +02:00
Philip H 6e7f2f730e Dockerfile: move all npm steps 2024-04-19 17:12:07 +02:00
Philip H c8224f34f9 Dockerfile: use nodejs 18 for build
workaround to get nodejs 20 working on older arm architectures
2024-04-19 17:12:07 +02:00
pheiduck 3032163814 bump to node 20 2024-04-19 17:12:07 +02:00
Philip H 2eec97ff25 Bugfix: differnt Ports usage (#973) 2024-04-19 17:11:50 +02:00
Philip H ae60493414 Merge branch 'master' into issue-972 2024-04-18 19:00:07 +02:00
Philip H e43688a091 npm: revert update-browserslist-db
is not needed
2024-04-18 18:50:29 +02:00
Philip H c29ba35d41 npm: update-browserslist-db 2024-04-18 18:47:52 +02:00
Peter Lewis a05b71c652 Update README.md
https://github.com/wg-easy/wg-easy/commit/2a3acdcad5b7c73feccdd47e4f3c5d743593e393#r141068572
2024-04-17 21:39:08 +01:00
Philip H e6b5f2e33c fixup: README 2024-04-17 19:22:07 +02:00
Philip H 187888e078 WireGuard.js: fixup ListenPort 2024-04-14 19:51:04 +02:00
Philip H f9daa3e5be Bugfix: differnt Ports usage 2024-04-14 19:51:04 +02:00
Philip H c2482f494a [combine] gitignore
Signed-off-by: Philip H <47042125+pheiduck@users.noreply.github.com>
2024-04-14 19:16:41 +02:00
Philip H ed369ff199 Revert "feat: cidr notation" (#969) 2024-04-06 15:21:41 +02:00
Philip H 2a3acdcad5 Merge branch 'master' into revert-888-feat-cidr-notation 2024-04-06 15:17:43 +02:00
Philip H 9be036aefa Merge branch 'production' into master 2024-04-05 19:04:13 +02:00
Philip H 9507454d3f [fixup] Grammar
Signed-off-by: Philip H <47042125+pheiduck@users.noreply.github.com>
2024-04-05 19:02:44 +02:00
Philip H 9e925c2ebb [prepare] version bump to 13 and updated changelog
Signed-off-by: Philip H <47042125+pheiduck@users.noreply.github.com>
2024-04-05 18:59:41 +02:00
Philip H 31c34367b2 Fix comment in docker-compose.yml (#960) 2024-04-03 12:34:28 +02:00
Michael van Tricht 990a7ae548 Fix comment in docker-compose.yml 2024-04-03 12:31:02 +02:00
Philip H 6dc75b7f1e i18n.js: complete words in Spanish (#956) 2024-04-01 23:39:35 +02:00
Rubén León 4868f32f1e i18n.js: complete words in Spanish 2024-04-01 23:16:57 +02:00
Philip H 61b57a885c README.md: make commands easier to copy 2024-04-01 18:04:29 +02:00
NPM Update Bot 074b3548d2 npm: package updates 2024-03-31 17:37:34 +00:00
Philip H 2dafeae54d Fix WG_DEFAULT_ADDRESS if not set (#951) 2024-03-31 19:36:57 +02:00
Philip H 2d7460f35a fix default WG_DEFAULT_ADDRESS issue (#948) 2024-03-31 19:32:22 +02:00
Utkarsh Goel 4981b72d00 fix unambiguous boolean operators 2024-03-27 22:23:37 +08:00
Utkarsh Goel 33634211a9 check for empty WG_DEFAULT_ADDRESS 2024-03-27 22:19:27 +08:00
Philip H 6045c2ada5 fix backward compatibility issue (#946) 2024-03-27 15:09:10 +01:00
Philip H bf214fb4d3 Revert "feat: cidr notation" 2024-03-27 14:41:31 +01:00
Philip H 9332e8b663 fix backward compatibility issue (#945)
in #888
fixes #943
2024-03-27 14:33:18 +01:00
Utkarsh Goel dbbfdd5357 Add backward compatibility for WG_DEFAULT_ADDRESS 2024-03-27 21:27:43 +08:00
NPM Update Bot cb63d5c67f npm: package updates 2024-03-26 16:40:32 +00:00
Philip H 3fce6e8d1d feat: cidr notation (#888) 2024-03-26 17:39:56 +01:00
Philip H 2f9364aa31 wg-easy.service: add missing WG_DEFAULT_ADDRESS_RANGE 2024-03-25 18:31:06 +01:00
Philip H 479c51d741 WG_DEFAULT_ADDRESS_RANGE / is not needed 2024-03-25 17:18:14 +01:00
Philip H 196cb63c6e README.md: add WG_DEFAULT_ADDRESS_RANGE 2024-03-25 17:15:30 +01:00
Philip H d024018bba fix cidr block calculation issue, fix post down config (#939)
Thanks for your work @utkarsh867!
2024-03-25 16:54:22 +01:00
Utkarsh Goel 5cdacd6cc3 Fix CIDR block calculation issue, fix POST_DOWN config 2024-03-25 23:28:36 +08:00
Philip H. fe7d77e481 fixup: packages 2024-03-23 20:34:43 +00:00
Philip H 6c0049770e Merge branch 'master' into feat-cidr-notation 2024-03-23 21:30:52 +01:00
Philip H f134a3671a fix: add Content-Type header for static files (#933) 2024-03-23 20:15:20 +01:00
cany748 8d00c5456a fix: add Content-Type header for static files 2024-03-23 19:37:33 +01:00
NPM Update Bot c2829d79e0 npm: package updates 2024-03-23 18:36:33 +00:00
Philip H ac47789561 revert: Workaround CVE-2023-42282
newest images have updated ip package
2024-03-23 19:35:58 +01:00
NPM Update Bot 5afb701013 npm: package updates 2024-03-21 10:42:32 +00:00
Philip H 81cae5e231 Use one command for updating with Docker Compose (#929) 2024-03-21 11:42:00 +01:00
Edgars 6c567d0082 Use one command for updating with Docker Compose
`README.md` was updated to use a one-liner to update WireGuard Easy with
Docker Compose.
A note about image tag was added to avoid confusion when one is
specified in Compose file and it is other than `latest`, as that would
result in no pull and no WireGuard Easy container recreation.
2024-03-21 09:02:08 +02:00
NPM Update Bot 62703ffbd6 npm: package updates 2024-03-20 12:38:50 +00:00
Philip H 19589e7ee7 refactor!: migrate from express to h3 (#914) 2024-03-20 13:38:14 +01:00
NPM Update Bot d40536c3fb npm: package updates 2024-03-19 21:10:54 +00:00
Philip H f979d23704 Add Chart and Auto/Light/Dark mode toggles (#925)
A banging contribution from @suxscribe. Many thanks!
2024-03-19 22:10:22 +01:00
Philip H 99c8081fe9 Add Chart and Auto/Light/Dark mode toggles (#924) 2024-03-19 21:24:14 +01:00
suxscribe 1c98e466c6 Merge branch 'feat-stats' into feat-toggles 2024-03-19 18:34:41 +03:00
Philip H 953a67bbdd fixup: Server.js 2024-03-19 14:18:29 +01:00
Philip H 5fbfb26937 fixup: Server.js 2024-03-19 14:16:44 +01:00
Philip H 74f3e2f320 fixup: Server.js 2024-03-19 14:11:03 +01:00
Philip H c107920df2 Merge branch 'master' into feat-h3 2024-03-19 14:09:42 +01:00
NPM Update Bot e666a14612 npm: package updates 2024-03-19 12:54:01 +00:00
Philip H a7ecb2a067 Fix TX/RX charts. Add UI_CHART_TYPE (#915) 2024-03-19 13:53:28 +01:00
Philip H 88b1b20e48 docker-compose.yml: add UI_CHART_TYPE 2024-03-18 21:25:08 +01:00
Sergei Birukov ac6a05f9be Fix lint errors 2024-03-18 21:25:08 +01:00
Sergei Birukov 262318df27 Disable debug 2024-03-18 21:25:08 +01:00
Sergei Birukov 98a5daf458 Fix charts on mobile 2024-03-18 21:25:08 +01:00
Sergei Birukov 71c208133d Remove .env from repo 2024-03-18 21:25:08 +01:00
Sergei Birukov aedb691b2b Fix traffic charts. Add chart vars
Add UI_TRAFFIC_STATS, UI_CHART_TYPE
2024-03-18 21:25:08 +01:00
Philip H ed0e46788a Update deploy.yml 2024-03-18 21:25:08 +01:00
Philip H 90bcdd1259 deploy.yml: rebuild v11
mistake
2024-03-18 21:25:08 +01:00
Sergei Birukov 81ccf8762d Header mobile layout 2024-03-18 10:05:12 +03:00
NPM Update Bot 90431ff9c5 npm: package updates 2024-03-18 00:03:46 +00:00
Sergei Birukov 32fc78589a Lint 2024-03-17 20:38:21 +03:00
Sergei Birukov f3a8ff6490 Add dark/light mode toggle. Add chart toggle
Change dark/light mode detection
Add saving uiShowCharts to local storage
Add saving uiTheme to local storage
Add ui buttons
2024-03-17 20:38:21 +03:00
Philip H f7bd362538 fixup: Server.js 2024-03-15 15:23:12 +01:00
Philip H 44cc5683d4 Merge branch 'master' into feat-h3 2024-03-15 15:22:17 +01:00
Philip H 4d5a5c9e0d docker-compose.yml: add UI_CHART_TYPE 2024-03-15 15:20:35 +01:00
Sergei Birukov 12b72cf389 Fix lint errors 2024-03-15 15:20:35 +01:00
Sergei Birukov ccde2fdfd3 Disable debug 2024-03-15 15:20:35 +01:00
Sergei Birukov 166a58a685 Fix charts on mobile 2024-03-15 15:20:35 +01:00
Sergei Birukov 73242c61c3 Remove .env from repo 2024-03-15 15:20:35 +01:00
Sergei Birukov 3d11730926 Fix traffic charts. Add chart vars
Add UI_TRAFFIC_STATS, UI_CHART_TYPE
2024-03-15 15:20:35 +01:00
Philip H 8ce2d44c08 Update deploy.yml 2024-03-15 15:20:35 +01:00
Philip H 3cb83dc53a deploy.yml: rebuild v11
mistake
2024-03-15 15:20:35 +01:00
NPM Update Bot e2242daef1 npm: package updates 2024-03-15 14:18:17 +00:00
Philip H ee5a2c6c5a Server.js: remove cookie setting
Proxy is recommended else use only internally
2024-03-15 15:17:41 +01:00
Philip H 68187e07a1 Merge branch 'master' into feat-cidr-notation 2024-03-14 13:49:26 +01:00
Philip H. 7596385d5a fixup: lint errors and uninstall express 2024-03-14 12:18:47 +00:00
Philip H 9a35b56f5c Update README.md 2024-03-14 09:33:08 +01:00
Philip H. 10f246ea59 fixup: Server.js 2024-03-14 08:19:29 +00:00
Philip H. 3f2495a0ea fixup: UI_TRAFFIC_STATS 2024-03-14 08:14:52 +00:00
Philip H. b7c2c81cc7 fixup: lint errors 2024-03-14 08:05:09 +00:00
Philip H b3306bee48 refactor!: migrate from express to h3 (#880) 2024-03-14 09:00:38 +01:00
Philip H 064e264ce8 Server.js: add missing ')' 2024-03-14 08:59:31 +01:00
Philip H 08c412fd14 Server.js: remove white-space 2024-03-14 08:57:38 +01:00
Philip H 8c771b054c Server.js: add UI_TRAFFIC_STATS 2024-03-14 08:55:15 +01:00
Philip H e9480a5ed5 fixup Server.js: even to master 2024-03-14 08:50:31 +01:00
Philip H 1a3cff855e Merge branch 'feat-h3' into master 2024-03-14 08:48:53 +01:00
Philip H f0ec8e45a0 i18n.js: add Hindi language translation (#902) 2024-03-13 18:55:40 +01:00
Rahil Bhimjiani d91f08eb1b i18n.js: add Hindi language translation
Signed-off-by: Rahil Bhimjiani <me@rahil.rocks>
2024-03-13 18:52:25 +01:00
Philip H 17fdb3396f Add setup and update instructions for Compose (#901) 2024-03-13 18:52:06 +01:00
Edgars 2f5878d406 Add setup and update instructions for Compose
`README.md` was updated to provide installation and update instructions
for Docker Compose as well.
2024-03-13 18:49:49 +01:00
NPM Update Bot 4dc439f041 npm: package updates 2024-03-13 17:49:02 +00:00
Philip H f69aa9dc65 PR: Build Docker images (#912) 2024-03-13 18:48:32 +01:00
Philip H 11a56ffdc5 needed for testing on our test branches 2024-03-13 13:12:16 +01:00
Philip H bad7bff98e Update deploy-pr.yml 2024-03-13 13:09:32 +01:00
Philip H a4c3bf0291 PR: Build Docker images
Signed-off-by: Philip H <47042125+pheiduck@users.noreply.github.com>
2024-03-13 13:05:08 +01:00
Philip H 2ffb68eeb2 [update] package.json and changelog to current release 12
Signed-off-by: Philip H <47042125+pheiduck@users.noreply.github.com>
2024-03-12 18:46:50 +01:00
Philip H a36ab8891e fixup: WireGuard.js 2024-03-06 16:43:07 +01:00
Philip H 5ee284b973 fixup: WireGuard.js 2024-03-05 19:35:38 +01:00
Philip H 754b5f29af fixup: WireGuard.js
well I was on the client side so I hope I get all stuff fixed now.
2024-03-05 19:29:14 +01:00
Philip H 84b2b61d63 Merge branch 'master' into feat-cidr-notation 2024-03-05 18:39:15 +01:00
Philip H 63faf4c507 fixup: WireGuard.js 2024-03-05 18:24:42 +01:00
Philip H 76a3d7f81d fixing stuff and formating 2024-03-05 18:15:42 +01:00
Philip H bbc919608c Update docker-compose.yml 2024-03-05 17:57:49 +01:00
Philip H 2f89765112 WireGuard.js: fixup undefined CIDR 2024-03-05 17:56:13 +01:00
Thomas Willems cb45bc1c43 update ip package to 2.0.1 2024-03-05 16:58:19 +01:00
Thomas Willems c4d4da38e7 correct CIDR notation 2024-03-05 16:58:19 +01:00
Thomas Willems 89415a2258 refactor to support CIDR and legacy notation
for WG_DEFAULT_ADDRESS
2024-03-05 16:58:19 +01:00
Thomas Willems 577af9947d introduce WG_DEFAULT_ADDRESS_RANGE (CIDR notation)
This PR allows the use of Address Ranges using the CIDR notation.

To make it backward compatible, i introduced a new env variable WG_DEFAULT_ADDRESS_RANGE (defaults to the previous default of 24).

This allows the usage of smaller subnets (or possibly larger; but i didn't test that due to restrictions on my network). Client IPs will be calculated with correct IP addresses instead of making assumptions of the address space.
2024-03-05 16:58:19 +01:00
cany748 7efdbf38e4 fix: revert ServerError.js 2024-02-28 16:27:33 +07:00
cany748 e8a160b14f refactor!: migrate from express to h3 2024-02-28 15:48:09 +07:00
26 changed files with 1342 additions and 1289 deletions
+1
View File
@@ -0,0 +1 @@
/src/node_modules
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) Emile Nijssen
# Founder and Codeowner of WireGuard Easy (wg-easy)
+2 -2
View File
@@ -24,13 +24,13 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
- OS: [e.g. iOS] - OS: [e.g. macOS 12.1]
- Browser [e.g. chrome, safari] - Browser [e.g. chrome, safari]
- Version [e.g. 22] - Version [e.g. 22]
**Smartphone (please complete the following information):** **Smartphone (please complete the following information):**
- Device: [e.g. iPhone6] - Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1] - OS: [e.g. iOS 8.1]
- Browser [e.g. stock browser, safari] - Browser [e.g. stock browser, safari]
- Version [e.g. 22] - Version [e.g. 22]
+38
View File
@@ -0,0 +1,38 @@
name: Build Pull Request
on:
workflow_dispatch:
pull_request:
jobs:
deploy:
name: Build & Deploy
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v4
with:
ref: production
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
uses: docker/build-push-action@v5
with:
push: false
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
tags: ghcr.io/wg-easy/wg-easy:pr
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '20'
check-latest: true check-latest: true
cache: 'npm' cache: 'npm'
cache-dependency-path: | cache-dependency-path: |
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '18' node-version: '20'
check-latest: true check-latest: true
cache: 'npm' cache: 'npm'
cache-dependency-path: | cache-dependency-path: |
+1
View File
@@ -1,5 +1,6 @@
/config /config
/wg0.conf /wg0.conf
/wg0.json /wg0.json
/src/node_modules
.DS_Store .DS_Store
*.swp *.swp
+8 -17
View File
@@ -1,17 +1,20 @@
# There's an issue with node:20-alpine. # As a workaround we have to build on nodejs 18
# Docker deployment is canceled after 25< minutes. # nodejs 20 hangs on build with armv6/armv7
FROM docker.io/library/node:18-alpine AS build_node_modules FROM docker.io/library/node:18-alpine AS build_node_modules
# Update npm to latest
RUN npm install -g npm@latest
# Copy Web UI # Copy Web UI
COPY src/ /app/ COPY src /app
WORKDIR /app WORKDIR /app
RUN npm ci --omit=dev &&\ RUN npm ci --omit=dev &&\
mv node_modules /node_modules mv node_modules /node_modules
# Copy build result to a new image. # Copy build result to a new image.
# This saves a lot of disk space. # This saves a lot of disk space.
FROM docker.io/library/node:18-alpine FROM docker.io/library/node:20-alpine
HEALTHCHECK CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1" --interval=1m --timeout=5s --retries=3
COPY --from=build_node_modules /app /app COPY --from=build_node_modules /app /app
# Move node_modules one directory up, so during development # Move node_modules one directory up, so during development
@@ -23,14 +26,6 @@ COPY --from=build_node_modules /app /app
# than what runs inside of docker. # than what runs inside of docker.
COPY --from=build_node_modules /node_modules /node_modules COPY --from=build_node_modules /node_modules /node_modules
RUN \
# Enable this to run `npm run serve`
npm i -g nodemon &&\
# Workaround CVE-2023-42282
npm uninstall -g ip &&\
# Delete unnecessary files
npm cache clean --force && rm -rf ~/.npm
# Install Linux packages # Install Linux packages
RUN apk add --no-cache \ RUN apk add --no-cache \
dpkg \ dpkg \
@@ -42,10 +37,6 @@ RUN apk add --no-cache \
# Use iptables-legacy # Use iptables-legacy
RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save
# Expose Ports
EXPOSE 51820/udp
EXPOSE 51821/tcp
# Set Environment # Set Environment
ENV DEBUG=Server,WireGuard ENV DEBUG=Server,WireGuard
+36 -10
View File
@@ -30,6 +30,17 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
* A host with a kernel that supports WireGuard (all modern kernels). * A host with a kernel that supports WireGuard (all modern kernels).
* A host with Docker installed. * A host with Docker installed.
## Versions
We provide more then 1 docker image to get, this will help you decide which one is best for you.
| tag | Branch | Example | Description |
| - | - | - | - |
| `latest` | production | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | stable as possbile get bug fixes quickly when needed, deployed against `production`. |
| `13` | production | `ghcr.io/wg-easy/wg-easy:13` | same as latest, stick to a version tag. |
| `nightly` | master | `ghcr.io/wg-easy/wg-easy:nightly` | mostly unstable gets frequent package and code updates, deployed against `master`. |
| `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs before landing into `master`. |
## Installation ## Installation
### 1. Install Docker ### 1. Install Docker
@@ -37,9 +48,9 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
If you haven't installed Docker yet, install it by running: If you haven't installed Docker yet, install it by running:
```bash ```bash
$ curl -sSL https://get.docker.com | sh curl -sSL https://get.docker.com | sh
$ sudo usermod -aG docker $(whoami) sudo usermod -aG docker $(whoami)
$ exit exit
``` ```
And log in again. And log in again.
@@ -48,12 +59,14 @@ And log in again.
To automatically install & run wg-easy, simply run: To automatically install & run wg-easy, simply run:
<pre> ```
$ docker run -d \ docker run -d \
--name=wg-easy \ --name=wg-easy \
-e LANG=de \ -e LANG=de \
-e WG_HOST=<b>🚨YOUR_SERVER_IP</b> \ -e WG_HOST=<🚨YOUR_SERVER_IP> \
-e PASSWORD=<b>🚨YOUR_ADMIN_PASSWORD</b> \ -e PASSWORD=<🚨YOUR_ADMIN_PASSWORD> \
-e PORT=51821 \
-e WG_PORT=51820 \
-v ~/.wg-easy:/etc/wireguard \ -v ~/.wg-easy:/etc/wireguard \
-p 51820:51820/udp \ -p 51820:51820/udp \
-p 51821:51821/tcp \ -p 51821:51821/tcp \
@@ -63,7 +76,7 @@ $ docker run -d \
--sysctl="net.ipv4.ip_forward=1" \ --sysctl="net.ipv4.ip_forward=1" \
--restart unless-stopped \ --restart unless-stopped \
ghcr.io/wg-easy/wg-easy ghcr.io/wg-easy/wg-easy
</pre> ```
> 💡 Replace `YOUR_SERVER_IP` with your WAN IP, or a Dynamic DNS hostname. > 💡 Replace `YOUR_SERVER_IP` with your WAN IP, or a Dynamic DNS hostname.
> >
@@ -73,6 +86,10 @@ The Web UI will now be available on `http://0.0.0.0:51821`.
> 💡 Your configuration files will be saved in `~/.wg-easy` > 💡 Your configuration files will be saved in `~/.wg-easy`
WireGuard Easy can be launched with Docker Compose as well - just download
[`docker-compose.yml`](docker-compose.yml), make necessary adjustments and
execute `docker compose up --detach`.
### 3. Sponsor ### 3. Sponsor
Are you enjoying this project? [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻 Are you enjoying this project? [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻
@@ -88,7 +105,7 @@ These options can be configured by setting environment variables using `-e KEY="
| `PASSWORD` | - | `foobar123` | When set, requires a password when logging in to the Web UI. | | `PASSWORD` | - | `foobar123` | When set, requires a password when logging in to the Web UI. |
| `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. | | `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. |
| `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. | | `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. |
| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will always listen on 51820 inside the Docker container. | | `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. |
| `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. | | `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. |
| `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. | | `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. |
| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. | | `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. |
@@ -98,8 +115,9 @@ These options can be configured by setting environment variables using `-e KEY="
| `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. | | `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. |
| `WG_PRE_DOWN` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L27) for the default value. | | `WG_PRE_DOWN` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L27) for the default value. |
| `WG_POST_DOWN` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L28) for the default value. | | `WG_POST_DOWN` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L28) for the default value. |
| `LANG` | `en` | `de` | Web UI language (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th). | | `LANG` | `en` | `de` | Web UI language (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi). |
| `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI | | `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI |
| `UI_CHART_TYPE` | `0` | `1` | UI_CHART_TYPE=0 # Charts disabled, UI_CHART_TYPE=1 # Line chart, UI_CHART_TYPE=2 # Area chart, UI_CHART_TYPE=3 # Bar chart |
> If you change `WG_PORT`, make sure to also change the exposed port. > If you change `WG_PORT`, make sure to also change the exposed port.
@@ -115,6 +133,14 @@ docker pull ghcr.io/wg-easy/wg-easy
And then run the `docker run -d \ ...` command above again. And then run the `docker run -d \ ...` command above again.
With Docker Compose WireGuard Easy can be updated with a single command:
`docker compose up --detach --pull always` (if an image tag is specified in the
Compose file and it is not `latest`, make sure that it is changed to the desired
one; by default it is omitted and
[defaults to `latest`](https://docs.docker.com/engine/reference/run/#image-references)). \
The WireGuared Easy container will be automatically recreated if a newer image
was pulled.
## Common Use Cases ## Common Use Cases
* [Using WireGuard-Easy with Pi-Hole](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-Pi-Hole) * [Using WireGuard-Easy with Pi-Hole](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-Pi-Hole)
-1
View File
@@ -1,4 +1,3 @@
version: "3.8"
services: services:
wg-easy: wg-easy:
image: wg-easy image: wg-easy
+4 -3
View File
@@ -1,4 +1,3 @@
version: "3.8"
volumes: volumes:
etc_wireguard: etc_wireguard:
@@ -6,7 +5,7 @@ services:
wg-easy: wg-easy:
environment: environment:
# Change Language: # Change Language:
# (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th) # (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi)
- LANG=de - LANG=de
# ⚠️ Required: # ⚠️ Required:
# Change this to your host's public address # Change this to your host's public address
@@ -14,6 +13,7 @@ services:
# Optional: # Optional:
# - PASSWORD=foobar123 # - PASSWORD=foobar123
# - PORT=51821
# - WG_PORT=51820 # - WG_PORT=51820
# - WG_DEFAULT_ADDRESS=10.8.0.x # - WG_DEFAULT_ADDRESS=10.8.0.x
# - WG_DEFAULT_DNS=1.1.1.1 # - WG_DEFAULT_DNS=1.1.1.1
@@ -24,7 +24,8 @@ services:
# - WG_POST_UP=echo "Post Up" > /etc/wireguard/post-up.txt # - WG_POST_UP=echo "Post Up" > /etc/wireguard/post-up.txt
# - WG_PRE_DOWN=echo "Pre Down" > /etc/wireguard/pre-down.txt # - WG_PRE_DOWN=echo "Pre Down" > /etc/wireguard/pre-down.txt
# - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt # - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt
# - UI_TRAFFIC_STATS=true # - UI_TRAFFIC_STATS=true
# - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart)
image: ghcr.io/wg-easy/wg-easy image: ghcr.io/wg-easy/wg-easy
container_name: wg-easy container_name: wg-easy
+3 -2
View File
@@ -9,6 +9,7 @@
"8": "Updated to Node.js v18.", "8": "Updated to Node.js v18.",
"9": "Fixed issue running on devices with older kernels.", "9": "Fixed issue running on devices with older kernels.",
"10": "Added sessionless HTTP API auth & automatic dark mode.", "10": "Added sessionless HTTP API auth & automatic dark mode.",
"11": "Multilanguage Support & various bugfixes", "11": "Multilanguage Support & various bugfixes.",
"12": "UI_TRAFFIC_STATS, Import json configurations with no PreShared-Key, allow clients with no privateKey & more." "12": "UI_TRAFFIC_STATS, Import json configurations with no PreShared-Key, allow clients with no privateKey & more.",
"13": "New framework (h3), UI_CHART_TYPE, some bugfixes and more."
} }
+1 -1
View File
@@ -2,7 +2,7 @@
"version": "1.0.1", "version": "1.0.1",
"scripts": { "scripts": {
"build": "DOCKER_BUILDKIT=1 docker build --tag wg-easy .", "build": "DOCKER_BUILDKIT=1 docker build --tag wg-easy .",
"serve": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up", "serve": "docker compose -f docker-compose.yml -f docker-compose.dev.yml up",
"start": "docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy" "start": "docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy"
} }
} }
-1
View File
@@ -1 +0,0 @@
/node_modules
+3 -2
View File
@@ -21,7 +21,7 @@ module.exports.WG_ALLOWED_IPS = process.env.WG_ALLOWED_IPS || '0.0.0.0/0, ::/0';
module.exports.WG_PRE_UP = process.env.WG_PRE_UP || ''; module.exports.WG_PRE_UP = process.env.WG_PRE_UP || '';
module.exports.WG_POST_UP = process.env.WG_POST_UP || ` module.exports.WG_POST_UP = process.env.WG_POST_UP || `
iptables -t nat -A POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${module.exports.WG_DEVICE} -j MASQUERADE; iptables -t nat -A POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${module.exports.WG_DEVICE} -j MASQUERADE;
iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A INPUT -p udp -m udp --dport ${module.exports.WG_PORT} -j ACCEPT;
iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT;
iptables -A FORWARD -o wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT;
`.split('\n').join(' '); `.split('\n').join(' ');
@@ -29,9 +29,10 @@ iptables -A FORWARD -o wg0 -j ACCEPT;
module.exports.WG_PRE_DOWN = process.env.WG_PRE_DOWN || ''; module.exports.WG_PRE_DOWN = process.env.WG_PRE_DOWN || '';
module.exports.WG_POST_DOWN = process.env.WG_POST_DOWN || ` module.exports.WG_POST_DOWN = process.env.WG_POST_DOWN || `
iptables -t nat -D POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${module.exports.WG_DEVICE} -j MASQUERADE; iptables -t nat -D POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${module.exports.WG_DEVICE} -j MASQUERADE;
iptables -D INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -D INPUT -p udp -m udp --dport ${module.exports.WG_PORT} -j ACCEPT;
iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT;
iptables -D FORWARD -o wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT;
`.split('\n').join(' '); `.split('\n').join(' ');
module.exports.LANG = process.env.LANG || 'en'; module.exports.LANG = process.env.LANG || 'en';
module.exports.UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false'; module.exports.UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false';
module.exports.UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0;
+175 -91
View File
@@ -1,15 +1,26 @@
'use strict'; 'use strict';
const path = require('path');
const bcrypt = require('bcryptjs');
const crypto = require('node:crypto'); const crypto = require('node:crypto');
const { createServer } = require('node:http');
const { stat, readFile } = require('node:fs/promises');
const { resolve, sep } = require('node:path');
const express = require('express');
const expressSession = require('express-session'); const expressSession = require('express-session');
const debug = require('debug')('Server'); const debug = require('debug')('Server');
const Util = require('./Util'); const {
const ServerError = require('./ServerError'); createApp,
createError,
createRouter,
defineEventHandler,
fromNodeMiddleware,
getRouterParam,
toNodeListener,
readBody,
setHeader,
serveStatic,
} = require('h3');
const WireGuard = require('../services/WireGuard'); const WireGuard = require('../services/WireGuard');
const { const {
@@ -19,41 +30,50 @@ const {
PASSWORD, PASSWORD,
LANG, LANG,
UI_TRAFFIC_STATS, UI_TRAFFIC_STATS,
UI_CHART_TYPE,
} = require('../config'); } = require('../config');
module.exports = class Server { module.exports = class Server {
constructor() { constructor() {
// Express const app = createApp();
this.app = express() this.app = app;
.disable('etag')
.use('/', express.static(path.join(__dirname, '..', 'www'))) app.use(fromNodeMiddleware(expressSession({
.use(express.json()) secret: crypto.randomBytes(256).toString('hex'),
.use(expressSession({ resave: true,
secret: crypto.randomBytes(256).toString('hex'), saveUninitialized: true,
resave: true, })));
saveUninitialized: true,
cookie: { const router = createRouter();
httpOnly: true, app.use(router);
},
router
.get('/api/release', defineEventHandler((event) => {
setHeader(event, 'Content-Type', 'application/json');
return RELEASE;
})) }))
.get('/api/release', (Util.promisify(async () => { .get('/api/lang', defineEventHandler((event) => {
return RELEASE; setHeader(event, 'Content-Type', 'application/json');
}))) return `"${LANG}"`;
}))
.get('/api/lang', (Util.promisify(async () => { .get('/api/ui-traffic-stats', defineEventHandler((event) => {
return LANG; setHeader(event, 'Content-Type', 'application/json');
}))) return `"${UI_TRAFFIC_STATS}"`;
.get('/api/ui-traffic-stats', (Util.promisify(async () => { }))
return UI_TRAFFIC_STATS === 'true';
})))
// Authentication .get('/api/ui-chart-type', defineEventHandler((event) => {
.get('/api/session', Util.promisify(async (req) => { setHeader(event, 'Content-Type', 'application/json');
return `"${UI_CHART_TYPE}"`;
}))
// Authentication
.get('/api/session', defineEventHandler((event) => {
const requiresPassword = !!process.env.PASSWORD; const requiresPassword = !!process.env.PASSWORD;
const authenticated = requiresPassword const authenticated = requiresPassword
? !!(req.session && req.session.authenticated) ? !!(event.node.req.session && event.node.req.session.authenticated)
: true; : true;
return { return {
@@ -61,28 +81,35 @@ module.exports = class Server {
authenticated, authenticated,
}; };
})) }))
.post('/api/session', Util.promisify(async (req) => { .post('/api/session', defineEventHandler(async (event) => {
const { const { password } = await readBody(event);
password,
} = req.body;
if (typeof password !== 'string') { if (typeof password !== 'string') {
throw new ServerError('Missing: Password', 401); throw createError({
status: 401,
message: 'Missing: Password',
});
} }
if (password !== PASSWORD) { if (password !== PASSWORD) {
throw new ServerError('Incorrect Password', 401); throw createError({
status: 401,
message: 'Incorrect Password',
});
} }
req.session.authenticated = true; event.node.req.session.authenticated = true;
req.session.save(); event.node.req.session.save();
debug(`New Session: ${req.session.id}`); debug(`New Session: ${event.node.req.session.id}`);
}))
return { succcess: true };
}));
// WireGuard // WireGuard
.use((req, res, next) => { app.use(
if (!PASSWORD) { fromNodeMiddleware((req, res, next) => {
if (!PASSWORD || !req.url.startsWith('/api/')) {
return next(); return next();
} }
@@ -90,37 +117,35 @@ module.exports = class Server {
return next(); return next();
} }
if (req.path.startsWith('/api/') && req.headers['authorization']) {
if (bcrypt.compareSync(req.headers['authorization'], bcrypt.hashSync(PASSWORD, 10))) {
return next();
}
return res.status(401).json({
error: 'Incorrect Password',
});
}
return res.status(401).json({ return res.status(401).json({
error: 'Not Logged In', error: 'Not Logged In',
}); });
}) }),
.delete('/api/session', Util.promisify(async (req) => { );
const sessionId = req.session.id;
req.session.destroy(); const router2 = createRouter();
app.use(router2);
router2
.delete('/api/session', defineEventHandler((event) => {
const sessionId = event.node.req.session.id;
event.node.req.session.destroy();
debug(`Deleted Session: ${sessionId}`); debug(`Deleted Session: ${sessionId}`);
return { success: true };
})) }))
.get('/api/wireguard/client', Util.promisify(async (req) => { .get('/api/wireguard/client', defineEventHandler(() => {
return WireGuard.getClients(); return WireGuard.getClients();
})) }))
.get('/api/wireguard/client/:clientId/qrcode.svg', Util.promisify(async (req, res) => { .get('/api/wireguard/client/:clientId/qrcode.svg', defineEventHandler(async (event) => {
const { clientId } = req.params; const clientId = getRouterParam(event, 'clientId');
const svg = await WireGuard.getClientQRCodeSVG({ clientId }); const svg = await WireGuard.getClientQRCodeSVG({ clientId });
res.header('Content-Type', 'image/svg+xml'); setHeader(event, 'Content-Type', 'image/svg+xml');
res.send(svg); return svg;
})) }))
.get('/api/wireguard/client/:clientId/configuration', Util.promisify(async (req, res) => { .get('/api/wireguard/client/:clientId/configuration', defineEventHandler(async (event) => {
const { clientId } = req.params; const clientId = getRouterParam(event, 'clientId');
const client = await WireGuard.getClient({ clientId }); const client = await WireGuard.getClient({ clientId });
const config = await WireGuard.getClientConfiguration({ clientId }); const config = await WireGuard.getClientConfiguration({ clientId });
const configName = client.name const configName = client.name
@@ -128,52 +153,111 @@ module.exports = class Server {
.replace(/(-{2,}|-$)/g, '-') .replace(/(-{2,}|-$)/g, '-')
.replace(/-$/, '') .replace(/-$/, '')
.substring(0, 32); .substring(0, 32);
res.header('Content-Disposition', `attachment; filename="${configName || clientId}.conf"`); setHeader(event, 'Content-Disposition', `attachment; filename="${configName || clientId}.conf"`);
res.header('Content-Type', 'text/plain'); setHeader(event, 'Content-Type', 'text/plain');
res.send(config); return config;
})) }))
.post('/api/wireguard/client', Util.promisify(async (req) => { .post('/api/wireguard/client', defineEventHandler(async (event) => {
const { name } = req.body; const { name } = await readBody(event);
return WireGuard.createClient({ name }); await WireGuard.createClient({ name });
return { success: true };
})) }))
.delete('/api/wireguard/client/:clientId', Util.promisify(async (req) => { .delete('/api/wireguard/client/:clientId', defineEventHandler(async (event) => {
const { clientId } = req.params; const clientId = getRouterParam(event, 'clientId');
return WireGuard.deleteClient({ clientId }); await WireGuard.deleteClient({ clientId });
return { success: true };
})) }))
.post('/api/wireguard/client/:clientId/enable', Util.promisify(async (req, res) => { .post('/api/wireguard/client/:clientId/enable', defineEventHandler(async (event) => {
const { clientId } = req.params; const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
res.end(403); throw createError({ status: 403 });
} }
return WireGuard.enableClient({ clientId }); await WireGuard.enableClient({ clientId });
return { success: true };
})) }))
.post('/api/wireguard/client/:clientId/disable', Util.promisify(async (req, res) => { .post('/api/wireguard/client/:clientId/disable', defineEventHandler(async (event) => {
const { clientId } = req.params; const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
res.end(403); throw createError({ status: 403 });
} }
return WireGuard.disableClient({ clientId }); await WireGuard.disableClient({ clientId });
return { success: true };
})) }))
.put('/api/wireguard/client/:clientId/name', Util.promisify(async (req, res) => { .put('/api/wireguard/client/:clientId/name', defineEventHandler(async (event) => {
const { clientId } = req.params; const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
res.end(403); throw createError({ status: 403 });
} }
const { name } = req.body; const { name } = await readBody(event);
return WireGuard.updateClientName({ clientId, name }); await WireGuard.updateClientName({ clientId, name });
return { success: true };
})) }))
.put('/api/wireguard/client/:clientId/address', Util.promisify(async (req, res) => { .put('/api/wireguard/client/:clientId/address', defineEventHandler(async (event) => {
const { clientId } = req.params; const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
res.end(403); throw createError({ status: 403 });
} }
const { address } = req.body; const { address } = await readBody(event);
return WireGuard.updateClientAddress({ clientId, address }); await WireGuard.updateClientAddress({ clientId, address });
})) return { success: true };
}));
.listen(PORT, WEBUI_HOST, () => { const safePathJoin = (base, target) => {
debug(`Listening on http://${WEBUI_HOST}:${PORT}`); // Manage web root (edge case)
if (target === '/') {
return `${base}${sep}`;
}
// Prepend './' to prevent absolute paths
const targetPath = `.${sep}${target}`;
// Resolve the absolute path
const resolvedPath = resolve(base, targetPath);
// Check if resolvedPath is a subpath of base
if (resolvedPath.startsWith(`${base}${sep}`)) {
return resolvedPath;
}
throw createError({
status: 400,
message: 'Bad Request',
}); });
};
// Static assets
const publicDir = '/app/www';
app.use(
defineEventHandler((event) => {
return serveStatic(event, {
getContents: (id) => {
return readFile(safePathJoin(publicDir, id));
},
getMeta: async (id) => {
const filePath = safePathJoin(publicDir, id);
const stats = await stat(filePath).catch(() => {});
if (!stats || !stats.isFile()) {
return;
}
if (id.endsWith('.html')) setHeader(event, 'Content-Type', 'text/html');
if (id.endsWith('.js')) setHeader(event, 'Content-Type', 'application/javascript');
if (id.endsWith('.json')) setHeader(event, 'Content-Type', 'application/json');
if (id.endsWith('.css')) setHeader(event, 'Content-Type', 'text/css');
if (id.endsWith('.png')) setHeader(event, 'Content-Type', 'image/png');
return {
size: stats.size,
mtime: stats.mtimeMs,
};
},
});
}),
);
createServer(toNodeListener(app)).listen(PORT, WEBUI_HOST);
debug(`Listening on http://${WEBUI_HOST}:${PORT}`);
} }
}; };
+4 -5
View File
@@ -1,10 +1,9 @@
'use strict'; 'use strict';
const fs = require('fs').promises; const fs = require('node:fs/promises');
const path = require('path'); const path = require('path');
const debug = require('debug')('WireGuard'); const debug = require('debug')('WireGuard');
const uuid = require('uuid'); const crypto = require('node:crypto');
const QRCode = require('qrcode'); const QRCode = require('qrcode');
const Util = require('./Util'); const Util = require('./Util');
@@ -95,7 +94,7 @@ module.exports = class WireGuard {
[Interface] [Interface]
PrivateKey = ${config.server.privateKey} PrivateKey = ${config.server.privateKey}
Address = ${config.server.address}/24 Address = ${config.server.address}/24
ListenPort = 51820 ListenPort = ${WG_PORT}
PreUp = ${WG_PRE_UP} PreUp = ${WG_PRE_UP}
PostUp = ${WG_POST_UP} PostUp = ${WG_POST_UP}
PreDown = ${WG_PRE_DOWN} PreDown = ${WG_PRE_DOWN}
@@ -248,7 +247,7 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
} }
// Create Client // Create Client
const id = uuid.v4(); const id = crypto.randomUUID();
const client = { const client = {
id, id,
name, name,
+526 -869
View File
File diff suppressed because it is too large Load Diff
+7 -8
View File
@@ -1,11 +1,11 @@
{ {
"release": "12", "release": "13",
"name": "wg-easy", "name": "wg-easy",
"version": "1.0.1", "version": "1.0.1",
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.", "description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"serve": "DEBUG=Server,WireGuard nodemon server.js", "serve": "DEBUG=Server,WireGuard npx nodemon server.js",
"serve-with-password": "PASSWORD=wg npm run serve", "serve-with-password": "PASSWORD=wg npm run serve",
"lint": "eslint .", "lint": "eslint .",
"buildcss": "npx tailwindcss -i ./www/src/css/app.css -o ./www/css/app.css" "buildcss": "npx tailwindcss -i ./www/src/css/app.css -o ./www/css/app.css"
@@ -13,16 +13,15 @@
"author": "Emile Nijssen", "author": "Emile Nijssen",
"license": "GPL", "license": "GPL",
"dependencies": { "dependencies": {
"bcryptjs": "^2.4.3",
"debug": "^4.3.4", "debug": "^4.3.4",
"express": "^4.18.3",
"express-session": "^1.18.0", "express-session": "^1.18.0",
"qrcode": "^1.5.3", "h3": "^1.11.1",
"uuid": "^9.0.1" "qrcode": "^1.5.3"
}, },
"devDependencies": { "devDependencies": {
"eslint-config-athom": "^3.1.3", "eslint-config-athom": "^3.1.3",
"tailwindcss": "^3.4.1" "nodemon": "^3.1.1",
"tailwindcss": "^3.4.3"
}, },
"nodemonConfig": { "nodemonConfig": {
"ignore": [ "ignore": [
@@ -30,6 +29,6 @@
] ]
}, },
"engines": { "engines": {
"node": "18" "node": ">=18"
} }
} }
+1 -1
View File
@@ -22,7 +22,7 @@ process.on('SIGTERM', async () => {
process.exit(0); process.exit(0);
}); });
// Handle interupt signal // Handle interrupt signal
process.on('SIGINT', () => { process.on('SIGINT', () => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('SIGINT signal received.'); console.log('SIGINT signal received.');
+1 -1
View File
@@ -3,7 +3,7 @@
'use strict'; 'use strict';
module.exports = { module.exports = {
darkMode: 'media', darkMode: 'selector',
content: ['./www/**/*.{html,js}'], content: ['./www/**/*.{html,js}'],
theme: { theme: {
screens: { screens: {
+306 -215
View File
@@ -1,5 +1,5 @@
/* /*
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com ! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com
*/ */
/* /*
@@ -211,6 +211,8 @@ textarea {
/* 1 */ /* 1 */
line-height: inherit; line-height: inherit;
/* 1 */ /* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit; color: inherit;
/* 1 */ /* 1 */
margin: 0; margin: 0;
@@ -234,9 +236,9 @@ select {
*/ */
button, button,
[type='button'], input:where([type='button']),
[type='reset'], input:where([type='reset']),
[type='submit'] { input:where([type='submit']) {
-webkit-appearance: button; -webkit-appearance: button;
/* 1 */ /* 1 */
background-color: transparent; background-color: transparent;
@@ -492,6 +494,10 @@ video {
--tw-backdrop-opacity: ; --tw-backdrop-opacity: ;
--tw-backdrop-saturate: ; --tw-backdrop-saturate: ;
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
} }
::backdrop { ::backdrop {
@@ -542,6 +548,10 @@ video {
--tw-backdrop-opacity: ; --tw-backdrop-opacity: ;
--tw-backdrop-saturate: ; --tw-backdrop-saturate: ;
--tw-backdrop-sepia: ; --tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
} }
.container { .container {
@@ -590,6 +600,18 @@ video {
} }
} }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.visible { .visible {
visibility: visible; visibility: visible;
} }
@@ -692,8 +714,8 @@ video {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
.mb-2 { .mb-4 {
margin-bottom: 0.5rem; margin-bottom: 1rem;
} }
.mb-5 { .mb-5 {
@@ -732,6 +754,10 @@ video {
margin-top: 0.75rem; margin-top: 0.75rem;
} }
.mt-4 {
margin-top: 1rem;
}
.mt-5 { .mt-5 {
margin-top: 1.25rem; margin-top: 1.25rem;
} }
@@ -804,10 +830,18 @@ video {
height: 1rem; height: 1rem;
} }
.h-5 {
height: 1.25rem;
}
.h-6 { .h-6 {
height: 1.5rem; height: 1.5rem;
} }
.h-8 {
height: 2rem;
}
.min-h-screen { .min-h-screen {
min-height: 100vh; min-height: 100vh;
} }
@@ -876,6 +910,10 @@ video {
flex-grow: 1; flex-grow: 1;
} }
.grow-0 {
flex-grow: 0;
}
.transform { .transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
} }
@@ -921,10 +959,18 @@ video {
flex-direction: column; flex-direction: column;
} }
.flex-col-reverse {
flex-direction: column-reverse;
}
.flex-wrap { .flex-wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }
.items-end {
align-items: flex-end;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
@@ -957,6 +1003,10 @@ video {
align-self: flex-start; align-self: flex-start;
} }
.self-end {
align-self: flex-end;
}
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }
@@ -1087,6 +1137,14 @@ video {
--tw-bg-opacity: 0.5; --tw-bg-opacity: 0.5;
} }
.fill-gray-400 {
fill: #9ca3af;
}
.fill-gray-600 {
fill: #4b5563;
}
.p-1 { .p-1 {
padding: 0.25rem; padding: 0.25rem;
} }
@@ -1141,11 +1199,6 @@ video {
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }
.py-5 {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.pb-1 { .pb-1 {
padding-bottom: 0.25rem; padding-bottom: 0.25rem;
} }
@@ -1276,6 +1329,11 @@ video {
color: rgb(17 24 39 / var(--tw-text-opacity)); color: rgb(17 24 39 / var(--tw-text-opacity));
} }
.text-neutral-400 {
--tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity));
}
.text-red-600 { .text-red-600 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(220 38 38 / var(--tw-text-opacity)); color: rgb(220 38 38 / var(--tw-text-opacity));
@@ -1463,10 +1521,24 @@ video {
opacity: 1; opacity: 1;
} }
.peer:checked ~ .peer-checked\:fill-gray-600 {
fill: #4b5563;
}
@media (min-width: 450px) { @media (min-width: 450px) {
.xxs\:flex-row { .xxs\:flex-row {
flex-direction: row; flex-direction: row;
} }
.xxs\:self-center {
align-self: center;
}
}
@media (min-width: 576px) {
.xs\:mt-6 {
margin-top: 1.5rem;
}
} }
@media (min-width: 640px) { @media (min-width: 640px) {
@@ -1597,6 +1669,11 @@ video {
padding-right: 0px; padding-right: 0px;
} }
.md\:py-5 {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.md\:pb-0 { .md\:pb-0 {
padding-bottom: 0px; padding-bottom: 0px;
} }
@@ -1607,208 +1684,222 @@ video {
} }
} }
@media (prefers-color-scheme: dark) { .dark\:border-neutral-500:where(.dark, .dark *) {
.dark\:border-neutral-500 { --tw-border-opacity: 1;
--tw-border-opacity: 1; border-color: rgb(115 115 115 / var(--tw-border-opacity));
border-color: rgb(115 115 115 / var(--tw-border-opacity)); }
}
.dark\:border-neutral-600:where(.dark, .dark *) {
.dark\:border-neutral-600 { --tw-border-opacity: 1;
--tw-border-opacity: 1; border-color: rgb(82 82 82 / var(--tw-border-opacity));
border-color: rgb(82 82 82 / var(--tw-border-opacity)); }
}
.dark\:border-neutral-800:where(.dark, .dark *) {
.dark\:border-neutral-800 { --tw-border-opacity: 1;
--tw-border-opacity: 1; border-color: rgb(38 38 38 / var(--tw-border-opacity));
border-color: rgb(38 38 38 / var(--tw-border-opacity)); }
}
.dark\:border-red-600:where(.dark, .dark *) {
.dark\:border-red-600 { --tw-border-opacity: 1;
--tw-border-opacity: 1; border-color: rgb(220 38 38 / var(--tw-border-opacity));
border-color: rgb(220 38 38 / var(--tw-border-opacity)); }
}
.dark\:bg-black:where(.dark, .dark *) {
.dark\:bg-black { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(0 0 0 / var(--tw-bg-opacity));
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); }
}
.dark\:bg-neutral-400:where(.dark, .dark *) {
.dark\:bg-neutral-400 { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(163 163 163 / var(--tw-bg-opacity));
background-color: rgb(163 163 163 / var(--tw-bg-opacity)); }
}
.dark\:bg-neutral-500:where(.dark, .dark *) {
.dark\:bg-neutral-500 { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(115 115 115 / var(--tw-bg-opacity));
background-color: rgb(115 115 115 / var(--tw-bg-opacity)); }
}
.dark\:bg-neutral-600:where(.dark, .dark *) {
.dark\:bg-neutral-600 { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(82 82 82 / var(--tw-bg-opacity));
background-color: rgb(82 82 82 / var(--tw-bg-opacity)); }
}
.dark\:bg-neutral-700:where(.dark, .dark *) {
.dark\:bg-neutral-700 { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(64 64 64 / var(--tw-bg-opacity));
background-color: rgb(64 64 64 / var(--tw-bg-opacity)); }
}
.dark\:bg-neutral-800:where(.dark, .dark *) {
.dark\:bg-neutral-800 { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(38 38 38 / var(--tw-bg-opacity));
background-color: rgb(38 38 38 / var(--tw-bg-opacity)); }
}
.dark\:bg-red-100:where(.dark, .dark *) {
.dark\:bg-red-100 { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(254 226 226 / var(--tw-bg-opacity));
background-color: rgb(254 226 226 / var(--tw-bg-opacity)); }
}
.dark\:bg-red-600:where(.dark, .dark *) {
.dark\:bg-red-600 { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(220 38 38 / var(--tw-bg-opacity));
background-color: rgb(220 38 38 / var(--tw-bg-opacity)); }
}
.dark\:bg-red-800:where(.dark, .dark *) {
.dark\:bg-red-800 { --tw-bg-opacity: 1;
--tw-bg-opacity: 1; background-color: rgb(153 27 27 / var(--tw-bg-opacity));
background-color: rgb(153 27 27 / var(--tw-bg-opacity)); }
}
.dark\:fill-neutral-400:where(.dark, .dark *) {
.dark\:text-gray-500 { fill: #a3a3a3;
--tw-text-opacity: 1; }
color: rgb(107 114 128 / var(--tw-text-opacity));
} .dark\:fill-neutral-600:where(.dark, .dark *) {
fill: #525252;
.dark\:text-neutral-200 { }
--tw-text-opacity: 1;
color: rgb(229 229 229 / var(--tw-text-opacity)); .dark\:text-gray-500:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
.dark\:text-neutral-300 { }
--tw-text-opacity: 1;
color: rgb(212 212 212 / var(--tw-text-opacity)); .dark\:text-neutral-200:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(229 229 229 / var(--tw-text-opacity));
.dark\:text-neutral-400 { }
--tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity)); .dark\:text-neutral-300:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(212 212 212 / var(--tw-text-opacity));
.dark\:text-neutral-50 { }
--tw-text-opacity: 1;
color: rgb(250 250 250 / var(--tw-text-opacity)); .dark\:text-neutral-400:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity));
.dark\:text-neutral-500 { }
--tw-text-opacity: 1;
color: rgb(115 115 115 / var(--tw-text-opacity)); .dark\:text-neutral-50:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(250 250 250 / var(--tw-text-opacity));
.dark\:text-neutral-600 { }
--tw-text-opacity: 1;
color: rgb(82 82 82 / var(--tw-text-opacity)); .dark\:text-neutral-500:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(115 115 115 / var(--tw-text-opacity));
.dark\:text-red-300 { }
--tw-text-opacity: 1;
color: rgb(252 165 165 / var(--tw-text-opacity)); .dark\:text-neutral-600:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(82 82 82 / var(--tw-text-opacity));
.dark\:text-red-600 { }
--tw-text-opacity: 1;
color: rgb(220 38 38 / var(--tw-text-opacity)); .dark\:text-red-300:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(252 165 165 / var(--tw-text-opacity));
.dark\:text-white { }
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity)); .dark\:text-red-600:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(220 38 38 / var(--tw-text-opacity));
.dark\:opacity-50 { }
opacity: 0.5;
} .dark\:text-white:where(.dark, .dark *) {
--tw-text-opacity: 1;
.dark\:placeholder\:text-neutral-400::-moz-placeholder { color: rgb(255 255 255 / var(--tw-text-opacity));
--tw-text-opacity: 1; }
color: rgb(163 163 163 / var(--tw-text-opacity));
} .dark\:opacity-50:where(.dark, .dark *) {
opacity: 0.5;
.dark\:placeholder\:text-neutral-400::placeholder { }
--tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity)); .dark\:placeholder\:text-neutral-400:where(.dark, .dark *)::-moz-placeholder {
} --tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity));
.dark\:placeholder\:text-neutral-500::-moz-placeholder { }
--tw-text-opacity: 1;
color: rgb(115 115 115 / var(--tw-text-opacity)); .dark\:placeholder\:text-neutral-400:where(.dark, .dark *)::placeholder {
} --tw-text-opacity: 1;
color: rgb(163 163 163 / var(--tw-text-opacity));
.dark\:placeholder\:text-neutral-500::placeholder { }
--tw-text-opacity: 1;
color: rgb(115 115 115 / var(--tw-text-opacity)); .dark\:placeholder\:text-neutral-500:where(.dark, .dark *)::-moz-placeholder {
} --tw-text-opacity: 1;
color: rgb(115 115 115 / var(--tw-text-opacity));
.dark\:hover\:border-neutral-600:hover { }
--tw-border-opacity: 1;
border-color: rgb(82 82 82 / var(--tw-border-opacity)); .dark\:placeholder\:text-neutral-500:where(.dark, .dark *)::placeholder {
} --tw-text-opacity: 1;
color: rgb(115 115 115 / var(--tw-text-opacity));
.dark\:hover\:border-red-600:hover { }
--tw-border-opacity: 1;
border-color: rgb(220 38 38 / var(--tw-border-opacity)); .dark\:hover\:border-neutral-600:hover:where(.dark, .dark *) {
} --tw-border-opacity: 1;
border-color: rgb(82 82 82 / var(--tw-border-opacity));
.dark\:hover\:bg-neutral-500:hover { }
--tw-bg-opacity: 1;
background-color: rgb(115 115 115 / var(--tw-bg-opacity)); .dark\:hover\:border-red-600:hover:where(.dark, .dark *) {
} --tw-border-opacity: 1;
border-color: rgb(220 38 38 / var(--tw-border-opacity));
.dark\:hover\:bg-neutral-600:hover { }
--tw-bg-opacity: 1;
background-color: rgb(82 82 82 / var(--tw-bg-opacity)); .dark\:hover\:bg-neutral-500:hover:where(.dark, .dark *) {
} --tw-bg-opacity: 1;
background-color: rgb(115 115 115 / var(--tw-bg-opacity));
.dark\:hover\:bg-red-600:hover { }
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity)); .dark\:hover\:bg-neutral-600:hover:where(.dark, .dark *) {
} --tw-bg-opacity: 1;
background-color: rgb(82 82 82 / var(--tw-bg-opacity));
.dark\:hover\:bg-red-700:hover { }
--tw-bg-opacity: 1;
background-color: rgb(185 28 28 / var(--tw-bg-opacity)); .dark\:hover\:bg-red-600:hover:where(.dark, .dark *) {
} --tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
.dark\:hover\:bg-red-800:hover { }
--tw-bg-opacity: 1;
background-color: rgb(153 27 27 / var(--tw-bg-opacity)); .dark\:hover\:bg-red-700:hover:where(.dark, .dark *) {
} --tw-bg-opacity: 1;
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
.dark\:hover\:text-neutral-700:hover { }
--tw-text-opacity: 1;
color: rgb(64 64 64 / var(--tw-text-opacity)); .dark\:hover\:bg-red-800:hover:where(.dark, .dark *) {
} --tw-bg-opacity: 1;
background-color: rgb(153 27 27 / var(--tw-bg-opacity));
.dark\:hover\:text-red-100:hover { }
--tw-text-opacity: 1;
color: rgb(254 226 226 / var(--tw-text-opacity)); .dark\:hover\:text-neutral-700:hover:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(64 64 64 / var(--tw-text-opacity));
.dark\:hover\:text-white:hover { }
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity)); .dark\:hover\:text-red-100:hover:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(254 226 226 / var(--tw-text-opacity));
.dark\:focus\:border-neutral-500:focus { }
--tw-border-opacity: 1;
border-color: rgb(115 115 115 / var(--tw-border-opacity)); .dark\:hover\:text-white:hover:where(.dark, .dark *) {
} --tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
.dark\:focus\:border-red-800:focus { }
--tw-border-opacity: 1;
border-color: rgb(153 27 27 / var(--tw-border-opacity)); .dark\:focus\:border-neutral-500:focus:where(.dark, .dark *) {
} --tw-border-opacity: 1;
border-color: rgb(115 115 115 / var(--tw-border-opacity));
.focus\:dark\:border-neutral-500:focus { }
--tw-border-opacity: 1;
border-color: rgb(115 115 115 / var(--tw-border-opacity)); .dark\:focus\:border-red-800:focus:where(.dark, .dark *) {
} --tw-border-opacity: 1;
border-color: rgb(153 27 27 / var(--tw-border-opacity));
}
.focus\:dark\:border-neutral-500:where(.dark, .dark *):focus {
--tw-border-opacity: 1;
border-color: rgb(115 115 115 / var(--tw-border-opacity));
}
.group:hover .group-hover\:dark\:fill-neutral-500:where(.dark, .dark *) {
fill: #737373;
}
.peer:checked ~ .peer-checked\:dark\:fill-neutral-400:where(.dark, .dark *) {
fill: #a3a3a3;
} }
+56 -24
View File
@@ -3,6 +3,7 @@
<head> <head>
<title>WireGuard</title> <title>WireGuard</title>
<meta charset="utf-8"/>
<link href="./css/app.css" rel="stylesheet"> <link href="./css/app.css" rel="stylesheet">
<link rel="manifest" href="./manifest.json"> <link rel="manifest" href="./manifest.json">
<link rel="icon" type="image/png" href="./img/favicon.png"> <link rel="icon" type="image/png" href="./img/favicon.png">
@@ -18,25 +19,56 @@
<body class="bg-gray-50 dark:bg-neutral-800"> <body class="bg-gray-50 dark:bg-neutral-800">
<div id="app"> <div id="app">
<div v-cloak class="container mx-auto max-w-3xl px-3 md:px-0"> <div v-cloak class="container mx-auto max-w-3xl px-3 md:px-0 mt-4 xs:mt-6">
<div v-if="authenticated === true"> <div v-if="authenticated === true">
<span v-if="requiresPassword" <div class="flex flex-col-reverse xxs:flex-row flex-auto items-center items-end gap-3">
class="text-sm text-gray-400 dark:text-neutral-400 mb-10 mr-2 mt-3 cursor-pointer hover:underline float-right" <h1 class="text-4xl dark:text-neutral-200 font-medium flex-grow self-start mb-4">
@click="logout"> <img src="./img/logo.png" width="32" class="inline align-middle dark:bg mr-2" /><span class="align-middle">WireGuard</span>
{{$t("logout")}} </h1>
<div class="flex items-center grow-0 gap-3 items-end self-end xxs:self-center">
<svg class="h-3 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" <!-- Dark / light theme -->
stroke="currentColor"> <button @click="toggleTheme"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 transition" :title="$t(`theme.${uiTheme}`)">
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /> <svg v-if="uiTheme === 'light'" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
</svg> class="w-5 h-5">
</span> <path stroke-linecap="round" stroke-linejoin="round"
<h1 class="text-4xl dark:text-neutral-200 font-medium mt-2 mb-2"> d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
<img src="./img/logo.png" width="32" class="inline align-middle dark:bg" /> </svg>
<span class="align-middle">WireGuard</span> <svg v-else-if="uiTheme === 'dark'" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
</h1> class="w-5 h-5 text-neutral-400">
<h2 class="text-sm text-gray-400 dark:text-neutral-400 mb-10"></h2> <path stroke-linecap="round" stroke-linejoin="round"
d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24"
class="w-5 h-5 fill-gray-600 dark:fill-neutral-400">
<path
d="M12,2.2c-5.4,0-9.8,4.4-9.8,9.8s4.4,9.8,9.8,9.8s9.8-4.4,9.8-9.8S17.4,2.2,12,2.2z M3.8,12c0-4.5,3.7-8.2,8.2-8.2v16.5C7.5,20.2,3.8,16.5,3.8,12z" />
</svg>
<path stroke-linecap="round" stroke-linejoin="round"
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25" />
</svg>
</button>
<!-- Show / hide charts -->
<label v-if="uiChartType > 0" class="inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap transition group" :title="$t('toggleCharts')">
<input type="checkbox" value="" class="sr-only peer" v-model="uiShowCharts" @change="toggleCharts">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" fill="currentColor"
class="w-5 h-5 peer fill-gray-400 peer-checked:fill-gray-600 dark:fill-neutral-600 peer-checked:dark:fill-neutral-400 group-hover:dark:fill-neutral-500 transition">
<path
d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z" />
</svg>
</label>
<span v-if="requiresPassword"
class="text-sm text-gray-400 dark:text-neutral-400 cursor-pointer hover:underline"
@click="logout">
{{$t("logout")}}
<svg class="h-3 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</span>
</div>
</div>
<div class="text-sm text-gray-400 dark:text-neutral-400 mb-5"></div>
<div v-if="latestRelease" <div v-if="latestRelease"
class="bg-red-800 dark:bg-red-100 p-4 text-white dark:text-red-600 text-sm font-small mb-10 rounded-md shadow-lg" class="bg-red-800 dark:bg-red-100 p-4 text-white dark:text-red-600 text-sm font-small mb-10 rounded-md shadow-lg"
:title="`v${currentRelease} → v${latestRelease.version}`"> :title="`v${currentRelease} → v${latestRelease.version}`">
@@ -77,17 +109,17 @@
class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid"> class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid">
<!-- Chart --> <!-- Chart -->
<div class="absolute z-0 bottom-0 left-0 right-0" style="top: 60%;"> <div v-if="uiChartType" class="absolute z-0 bottom-0 left-0 right-0 h-6" >
<apexchart width="100%" height="100%" :options="client.chartOptions" :series="client.transferTxSeries"> <apexchart width="100%" height="100%" :options="chartOptionsTX" :series="client.transferTxSeries">
</apexchart> </apexchart>
</div> </div>
<div class="absolute z-0 top-0 left-0 right-0" style="bottom: 60%;"> <div v-if="uiChartType" class="absolute z-0 top-0 left-0 right-0 h-6" >
<apexchart width="100%" height="100%" :options="client.chartOptions" :series="client.transferRxSeries" <apexchart width="100%" height="100%" :options="chartOptionsRX" :series="client.transferRxSeries"
style="transform: scaleY(-1);"> style="transform: scaleY(-1);">
</apexchart> </apexchart>
</div> </div>
<div class="relative py-5 px-3 z-10 flex flex-col sm:flex-row justify-between gap-3"> <div class="relative py-3 md:py-5 px-3 z-10 flex flex-col sm:flex-row justify-between gap-3">
<div class="flex gap-3 md:gap-4 w-full items-center "> <div class="flex gap-3 md:gap-4 w-full items-center ">
<!-- Avatar --> <!-- Avatar -->
@@ -542,7 +574,7 @@
</div> </div>
<p v-cloak class="text-center m-10 text-gray-300 dark:text-neutral-600 text-xs"> <a class="hover:underline" target="_blank" <p v-cloak class="text-center m-10 text-gray-300 dark:text-neutral-600 text-xs"> <a class="hover:underline" target="_blank"
href="https://github.com/wg-easy/wg-easy">WireGuard Easy</a> © 2021-2024 by <a class="hover:underline" target="_blank" href="https://github.com/wg-easy/wg-easy">WireGuard Easy</a> © 2021-2024 by <a class="hover:underline" target="_blank"
href="https://emilenijssen.nl/?ref=wg-easy">Emile Nijssen</a> is licensed under <a class="hover:underline" target="_blank" href="https://emilenijssen.nl/?ref=wg-easy">Emile Nijssen</a> is licensed under <a class="hover:underline" target="_blank"
href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> · <a class="hover:underline" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> · <a class="hover:underline"
+7
View File
@@ -50,6 +50,13 @@ class API {
}); });
} }
async getChartType() {
return this.call({
method: 'get',
path: '/ui-chart-type',
});
}
async getSession() { async getSession() {
return this.call({ return this.call({
method: 'get', method: 'get',
+126 -34
View File
@@ -29,8 +29,24 @@ const i18n = new VueI18n({
messages, messages,
}); });
const UI_CHART_TYPES = [
{ type: false, strokeWidth: 0 },
{ type: 'line', strokeWidth: 3 },
{ type: 'area', strokeWidth: 0 },
{ type: 'bar', strokeWidth: 0 },
];
const CHART_COLORS = {
rx: { light: 'rgba(128,128,128,0.3)', dark: 'rgba(255,255,255,0.3)' },
tx: { light: 'rgba(128,128,128,0.4)', dark: 'rgba(255,255,255,0.3)' },
gradient: { light: ['rgba(0,0,0,1.0)', 'rgba(0,0,0,1.0)'], dark: ['rgba(128,128,128,0)', 'rgba(128,128,128,0)'] },
};
new Vue({ new Vue({
el: '#app', el: '#app',
components: {
apexchart: VueApexCharts,
},
i18n, i18n,
data: { data: {
authenticated: null, authenticated: null,
@@ -52,13 +68,16 @@ new Vue({
currentRelease: null, currentRelease: null,
latestRelease: null, latestRelease: null,
isDark: null,
uiTrafficStats: false, uiTrafficStats: false,
uiChartType: 0,
uiShowCharts: localStorage.getItem('uiShowCharts') === '1',
uiTheme: localStorage.theme || 'auto',
prefersDarkScheme: window.matchMedia('(prefers-color-scheme: dark)'),
chartOptions: { chartOptions: {
chart: { chart: {
background: 'transparent', background: 'transparent',
type: 'bar',
stacked: false, stacked: false,
toolbar: { toolbar: {
show: false, show: false,
@@ -66,11 +85,27 @@ new Vue({
animations: { animations: {
enabled: false, enabled: false,
}, },
parentHeightOffset: 0,
sparkline: {
enabled: true,
},
},
colors: [],
stroke: {
curve: 'smooth',
},
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
type: 'vertical',
shadeIntensity: 0,
gradientToColors: CHART_COLORS.gradient[this.theme],
inverseColors: false,
opacityTo: 0,
stops: [0, 100],
},
}, },
colors: [
'#DDDDDD', // rx
'#EEEEEE', // tx
],
dataLabels: { dataLabels: {
enabled: false, enabled: false,
}, },
@@ -84,10 +119,10 @@ new Vue({
show: false, show: false,
}, },
axisTicks: { axisTicks: {
show: true, show: false,
}, },
axisBorder: { axisBorder: {
show: true, show: false,
}, },
}, },
yaxis: { yaxis: {
@@ -153,27 +188,42 @@ new Vue({
// Debug // Debug
// client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; // client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000;
// client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; // client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000;
// client.latestHandshakeAt = new Date();
// this.requiresPassword = true;
this.clientsPersist[client.id].transferRxCurrent = client.transferRx - this.clientsPersist[client.id].transferRxPrevious;
this.clientsPersist[client.id].transferRxPrevious = client.transferRx;
this.clientsPersist[client.id].transferTxCurrent = client.transferTx - this.clientsPersist[client.id].transferTxPrevious;
this.clientsPersist[client.id].transferTxPrevious = client.transferTx;
if (updateCharts) { if (updateCharts) {
this.clientsPersist[client.id].transferRxCurrent = client.transferRx - this.clientsPersist[client.id].transferRxPrevious;
this.clientsPersist[client.id].transferRxPrevious = client.transferRx;
this.clientsPersist[client.id].transferTxCurrent = client.transferTx - this.clientsPersist[client.id].transferTxPrevious;
this.clientsPersist[client.id].transferTxPrevious = client.transferTx;
this.clientsPersist[client.id].transferRxHistory.push(this.clientsPersist[client.id].transferRxCurrent); this.clientsPersist[client.id].transferRxHistory.push(this.clientsPersist[client.id].transferRxCurrent);
this.clientsPersist[client.id].transferRxHistory.shift(); this.clientsPersist[client.id].transferRxHistory.shift();
this.clientsPersist[client.id].transferTxHistory.push(this.clientsPersist[client.id].transferTxCurrent); this.clientsPersist[client.id].transferTxHistory.push(this.clientsPersist[client.id].transferTxCurrent);
this.clientsPersist[client.id].transferTxHistory.shift(); this.clientsPersist[client.id].transferTxHistory.shift();
this.clientsPersist[client.id].transferTxSeries = [{
name: 'Tx',
data: this.clientsPersist[client.id].transferTxHistory,
}];
this.clientsPersist[client.id].transferRxSeries = [{
name: 'Rx',
data: this.clientsPersist[client.id].transferRxHistory,
}];
client.transferTxHistory = this.clientsPersist[client.id].transferTxHistory;
client.transferRxHistory = this.clientsPersist[client.id].transferRxHistory;
client.transferMax = Math.max(...client.transferTxHistory, ...client.transferRxHistory);
client.transferTxSeries = this.clientsPersist[client.id].transferTxSeries;
client.transferRxSeries = this.clientsPersist[client.id].transferRxSeries;
} }
client.transferTxCurrent = this.clientsPersist[client.id].transferTxCurrent; client.transferTxCurrent = this.clientsPersist[client.id].transferTxCurrent;
client.transferRxCurrent = this.clientsPersist[client.id].transferRxCurrent; client.transferRxCurrent = this.clientsPersist[client.id].transferRxCurrent;
client.transferTxHistory = this.clientsPersist[client.id].transferTxHistory;
client.transferRxHistory = this.clientsPersist[client.id].transferRxHistory;
client.transferMax = Math.max(...client.transferTxHistory, ...client.transferRxHistory);
client.hoverTx = this.clientsPersist[client.id].hoverTx; client.hoverTx = this.clientsPersist[client.id].hoverTx;
client.hoverRx = this.clientsPersist[client.id].hoverRx; client.hoverRx = this.clientsPersist[client.id].hoverRx;
@@ -250,14 +300,25 @@ new Vue({
.finally(() => this.refresh().catch(console.error)); .finally(() => this.refresh().catch(console.error));
}, },
toggleTheme() { toggleTheme() {
if (this.isDark) { const themes = ['light', 'dark', 'auto'];
localStorage.theme = 'light'; const currentIndex = themes.indexOf(this.uiTheme);
document.documentElement.classList.remove('dark'); const newIndex = (currentIndex + 1) % themes.length;
} else { this.uiTheme = themes[newIndex];
localStorage.theme = 'dark'; localStorage.theme = this.uiTheme;
document.documentElement.classList.add('dark'); this.setTheme(this.uiTheme);
},
setTheme(theme) {
const { classList } = document.documentElement;
const shouldAddDarkClass = theme === 'dark' || (theme === 'auto' && this.prefersDarkScheme.matches);
classList.toggle('dark', shouldAddDarkClass);
},
handlePrefersChange(e) {
if (localStorage.theme === 'auto') {
this.setTheme(e.matches ? 'dark' : 'light');
} }
this.isDark = !this.isDark; },
toggleCharts() {
localStorage.setItem('uiShowCharts', this.uiShowCharts ? 1 : 0);
}, },
}, },
filters: { filters: {
@@ -267,10 +328,8 @@ new Vue({
}, },
}, },
mounted() { mounted() {
this.isDark = false; this.prefersDarkScheme.addListener(this.handlePrefersChange);
if (localStorage.theme === 'dark') { this.setTheme(this.uiTheme);
this.isDark = true;
}
this.api = new API(); this.api = new API();
this.api.getSession() this.api.getSession()
@@ -278,7 +337,7 @@ new Vue({
this.authenticated = session.authenticated; this.authenticated = session.authenticated;
this.requiresPassword = session.requiresPassword; this.requiresPassword = session.requiresPassword;
this.refresh({ this.refresh({
updateCharts: true, updateCharts: this.updateCharts,
}).catch((err) => { }).catch((err) => {
alert(err.message || err.toString()); alert(err.message || err.toString());
}); });
@@ -289,7 +348,7 @@ new Vue({
setInterval(() => { setInterval(() => {
this.refresh({ this.refresh({
updateCharts: true, updateCharts: this.updateCharts,
}).catch(console.error); }).catch(console.error);
}, 1000); }, 1000);
@@ -298,10 +357,17 @@ new Vue({
this.uiTrafficStats = res; this.uiTrafficStats = res;
}) })
.catch(() => { .catch(() => {
console.log('Failed to get ui-traffic-stats');
this.uiTrafficStats = false; this.uiTrafficStats = false;
}); });
this.api.getChartType()
.then((res) => {
this.uiChartType = parseInt(res, 10);
})
.catch(() => {
this.uiChartType = 0;
});
Promise.resolve().then(async () => { Promise.resolve().then(async () => {
const lang = await this.api.getLang(); const lang = await this.api.getLang();
if (lang !== localStorage.getItem('lang') && i18n.availableLocales.includes(lang)) { if (lang !== localStorage.getItem('lang') && i18n.availableLocales.includes(lang)) {
@@ -324,13 +390,39 @@ new Vue({
return releasesArray[0]; return releasesArray[0];
}); });
console.log(`Current Release: ${currentRelease}`);
console.log(`Latest Release: ${latestRelease.version}`);
if (currentRelease >= latestRelease.version) return; if (currentRelease >= latestRelease.version) return;
this.currentRelease = currentRelease; this.currentRelease = currentRelease;
this.latestRelease = latestRelease; this.latestRelease = latestRelease;
}).catch((err) => console.error(err)); }).catch((err) => console.error(err));
}, },
computed: {
chartOptionsTX() {
const opts = {
...this.chartOptions,
colors: [CHART_COLORS.tx[this.theme]],
};
opts.chart.type = UI_CHART_TYPES[this.uiChartType].type || false;
opts.stroke.width = UI_CHART_TYPES[this.uiChartType].strokeWidth;
return opts;
},
chartOptionsRX() {
const opts = {
...this.chartOptions,
colors: [CHART_COLORS.rx[this.theme]],
};
opts.chart.type = UI_CHART_TYPES[this.uiChartType].type || false;
opts.stroke.width = UI_CHART_TYPES[this.uiChartType].strokeWidth;
return opts;
},
updateCharts() {
return this.uiChartType > 0 && this.uiShowCharts;
},
theme() {
if (this.uiTheme === 'auto') {
return this.prefersDarkScheme.matches ? 'dark' : 'light';
}
return this.uiTheme;
},
},
}); });
+32
View File
@@ -28,6 +28,8 @@ const messages = { // eslint-disable-line no-unused-vars
downloadConfig: 'Download Configuration', downloadConfig: 'Download Configuration',
madeBy: 'Made by', madeBy: 'Made by',
donate: 'Donate', donate: 'Donate',
toggleCharts: 'Show/hide Charts',
theme: { dark: 'Dark theme', light: 'Light theme', auto: 'Auto theme' },
}, },
ua: { ua: {
name: 'Ім`я', name: 'Ім`я',
@@ -273,6 +275,8 @@ const messages = { // eslint-disable-line no-unused-vars
downloadConfig: 'Descargar configuración', downloadConfig: 'Descargar configuración',
madeBy: 'Hecho por', madeBy: 'Hecho por',
donate: 'Donar', donate: 'Donar',
toggleCharts: 'Mostrar/Ocultar gráficos',
theme: { dark: 'Modo oscuro', light: 'Modo claro', auto: 'Modo automático' },
}, },
ko: { ko: {
name: '이름', name: '이름',
@@ -517,4 +521,32 @@ const messages = { // eslint-disable-line no-unused-vars
madeBy: 'สร้างโดย', madeBy: 'สร้างโดย',
donate: 'บริจาค', donate: 'บริจาค',
}, },
hi: { // github.com/rahilarious
name: 'नाम',
password: 'पासवर्ड',
signIn: 'लॉगिन',
logout: 'लॉगआउट',
updateAvailable: 'अपडेट उपलब्ध है!',
update: 'अपडेट',
clients: 'उपयोगकर्ताये',
new: 'नया',
deleteClient: 'उपयोगकर्ता हटाएँ',
deleteDialog1: 'क्या आपको पक्का हटाना है',
deleteDialog2: 'यह निर्णय पलट नहीं सकता।',
cancel: 'कुछ ना करें',
create: 'बनाएं',
createdOn: 'सर्जन तारीख ',
lastSeen: 'पिछली बार देखे गए थे ',
totalDownload: 'कुल डाउनलोड: ',
totalUpload: 'कुल अपलोड: ',
newClient: 'नया उपयोगकर्ता',
disableClient: 'उपयोगकर्ता स्थगित कीजिये',
enableClient: 'उपयोगकर्ता शुरू कीजिये',
noClients: 'अभी तक कोई भी उपयोगकर्ता नहीं है।',
noPrivKey: 'ये उपयोगकर्ता की कोई भी गुप्त चाबी नहीं हे। बना नहीं सकते।',
showQR: 'क्यू आर कोड देखिये',
downloadConfig: 'डाउनलोड कॉन्फीग्यूरेशन',
madeBy: 'सर्जक',
donate: 'दान करें',
},
}; };