Compare commits

...

1292 Commits
v4.0.1 ... main

Author SHA1 Message Date
Donald Zou 92651729eb
Update funding information in FUNDING.yml 2025-12-18 07:35:06 +08:00
Donald Zou d51c543346
Merge pull request #1045 from WGDashboard/DaanSelen-patch-1
chore: english refac
2025-12-17 07:36:35 +08:00
DaanSelen 3adca64624
Update README.md 2025-12-17 00:06:12 +01:00
Daan Selen 306e17eb4d refac: some exit logic 2025-12-14 20:56:18 +01:00
DaanSelen d75c7ad418
feat: add better docker stopping response (#1025) 2025-12-14 17:32:46 +01:00
Donald Zou 82e10659b6
Update Wakatime badge link in README.md 2025-12-14 00:28:24 +08:00
Donald Zou b6f25ac817
Include docker directory in workflow paths 2025-12-14 00:25:37 +08:00
Donald Zou 48481ab992
Implement path filter for Docker workflow triggers
Added a path filter to restrict Docker workflow triggers to changes in the 'src' directory.
2025-12-14 00:24:21 +08:00
Donald Zou d56a3cb5d6
Merge pull request #1031 from WGDashboard/v4.3.2-dev
v4.3.1
2025-12-13 23:26:41 +08:00
Donald Zou d6a930c04b Bumped version number 2025-12-13 17:20:56 +08:00
Donald Zou 6fe257fa4a Update entrypoint.sh 2025-12-13 16:29:48 +08:00
Donald Zou e5d1a64d7d Update to Python 3.14-alpine and refactor IP parsing
Bump Dockerfile base image from python:3.13.3-alpine to python:3.14-alpine for both build and runtime stages. Refactor IP address parsing in dashboard.py for improved clarity and variable naming.
2025-12-13 16:05:12 +08:00
Donald Zou da4cec60e5
Update Dockerfile 2025-12-12 23:41:25 +08:00
DaanSelen a4d471df4f
chore: downgrade python due to compatibility 2025-12-12 16:32:48 +01:00
DaanSelen f00940b73c chore: cleanup 2025-12-12 15:27:04 +01:00
DaanSelen cb9cb4a0b6 chore: fix docker (hopefully) 2025-12-12 15:17:25 +01:00
DaanSelen b695ccc5f9
chore: add openresolv back into the dependencies to blanked cover our deps 2025-12-11 23:34:46 +01:00
DaanSelen 16cd4da5fc
chore: add openrc to avoid no init systems (#1027) 2025-12-11 21:56:44 +01:00
Donald Zou 7be830a34e
Merge pull request #1024 from WGDashboard/v4.3.0.3-dev
Bumped version, corrected some vue package after update
2025-12-11 07:03:58 +08:00
Donald Zou eb65d6fb2a Bumped version, corrected some vue package after update 2025-12-11 07:03:31 +08:00
Daan Selen 6fee7da633 chore: fix bcrypt issue 2025-12-10 23:17:45 +01:00
DaanSelen 2e8d2fe400
chore: bump WGDashboard version 2025-12-10 22:59:35 +01:00
DaanSelen da3c34c250 chore: rename workflow 2025-12-10 12:19:25 +01:00
dependabot[bot] d864d12793
build(deps): bump @vue/language-server in /src/static/app (#1019)
Bumps [@vue/language-server](https://github.com/vuejs/language-tools/tree/HEAD/packages/language-server) from 3.1.3 to 3.1.7.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.1.7/packages/language-server)

---
updated-dependencies:
- dependency-name: "@vue/language-server"
  dependency-version: 3.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 13:36:19 +01:00
DaanSelen 36390a4d71
refac: rework tailing of logs in the Docker entrypoint (#1021)
* chore: add debug workflow

* chore: testing fix

* chore: try next option

* refac: tailing logs

---------

Co-authored-by: DaanSelen <dselen@systemec.nl>
2025-12-09 13:16:18 +01:00
DaanSelen 5db2baf1dc
fix sed command for armv7 2025-12-09 10:18:39 +01:00
DaanSelen dc10da607d
chore: bump codeql 2025-12-09 10:06:27 +01:00
dependabot[bot] 9b4abf19fa
build(deps): bump @vueuse/shared in /src/static/app (#1015)
Bumps [@vueuse/shared](https://github.com/vueuse/vueuse/tree/HEAD/packages/shared) from 14.0.0 to 14.1.0.
- [Release notes](https://github.com/vueuse/vueuse/releases)
- [Commits](https://github.com/vueuse/vueuse/commits/v14.1.0/packages/shared)

---
updated-dependencies:
- dependency-name: "@vueuse/shared"
  dependency-version: 14.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 09:55:57 +01:00
dependabot[bot] bf823e6cf8
build(deps): bump @volar/language-server in /src/static/app (#1017)
Bumps [@volar/language-server](https://github.com/volarjs/volar.js/tree/HEAD/packages/language-server) from 2.4.23 to 2.4.26.
- [Release notes](https://github.com/volarjs/volar.js/releases)
- [Changelog](https://github.com/volarjs/volar.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/volarjs/volar.js/commits/v2.4.26/packages/language-server)

---
updated-dependencies:
- dependency-name: "@volar/language-server"
  dependency-version: 2.4.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 09:55:47 +01:00
dependabot[bot] 880c94b8a3
build(deps): bump npm from 11.6.2 to 11.6.4 in /src/static/app (#1018)
Bumps [npm](https://github.com/npm/cli) from 11.6.2 to 11.6.4.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v11.6.2...v11.6.4)

---
updated-dependencies:
- dependency-name: npm
  dependency-version: 11.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 09:55:35 +01:00
dependabot[bot] 656eba3a57
build(deps): bump @vuepic/vue-datepicker in /src/static/app (#1020)
Bumps [@vuepic/vue-datepicker](https://github.com/Vuepic/vue-datepicker) from 12.0.1 to 12.1.0.
- [Release notes](https://github.com/Vuepic/vue-datepicker/releases)
- [Changelog](https://github.com/Vuepic/vue-datepicker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Vuepic/vue-datepicker/compare/v12.0.1...v12.1.0)

---
updated-dependencies:
- dependency-name: "@vuepic/vue-datepicker"
  dependency-version: 12.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 09:55:17 +01:00
dependabot[bot] 4154a27f4d
build(deps): bump pydantic from 2.12.4 to 2.12.5 in /src (#1014)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.12.4 to 2.12.5.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.12.4...v2.12.5)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-version: 2.12.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 09:54:41 +01:00
dependabot[bot] 785d0d6b06
build(deps): bump psycopg[binary] from 3.2.13 to 3.3.2 in /src (#1016)
Bumps [psycopg[binary]](https://github.com/psycopg/psycopg) from 3.2.13 to 3.3.2.
- [Changelog](https://github.com/psycopg/psycopg/blob/master/docs/news.rst)
- [Commits](https://github.com/psycopg/psycopg/compare/3.2.13...3.3.2)

---
updated-dependencies:
- dependency-name: psycopg[binary]
  dependency-version: 3.3.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 09:54:21 +01:00
DaanSelen 9af2983a7b chore: absolute paths and robuust checking 2025-12-08 14:51:28 +01:00
Donald Zou 83b4753d5f
Merge pull request #1012 from WGDashboard/v4.3.0.2-dev
v4.3.0.2 Merge
2025-12-08 16:53:18 +08:00
Donald Zou f4f0ab27ef Final Testing 2025-12-07 11:20:48 +08:00
Donald Zou d51815e0f7
Merge pull request #1010 from WGDashboard/version-bump-v4.3.0.2
Update Version for Admin App
2025-12-06 23:44:08 +08:00
Donald Zou 413b65e976
Merge branch 'main' into version-bump-v4.3.0.2 2025-12-06 23:42:24 +08:00
Donald Zou b1ea6d3c1d Update Version for Admin App 2025-12-06 23:39:56 +08:00
Donald Zou c7bed91626
Merge pull request #1009 from WGDashboard/version-bump-v4.3.0.2
Version Bump to v4.3.0.2
2025-12-06 23:34:11 +08:00
Donald Zou b9e09bf91b Update DashboardConfig.py 2025-12-06 23:33:38 +08:00
Donald Zou 839f686d73
Merge pull request #1005 from WGDashboard/Manage-Tracking-Feature
Manage tracking feature
2025-12-06 23:28:58 +08:00
dependabot[bot] 1ead86dacc
build(deps): bump vue from 3.5.22 to 3.5.24 in /src/static/app (#989)
Bumps [vue](https://github.com/vuejs/core) from 3.5.22 to 3.5.24.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.22...v3.5.24)

---
updated-dependencies:
- dependency-name: vue
  dependency-version: 3.5.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-05 12:06:41 +01:00
dependabot[bot] c4265b31a9
build(deps): bump ol from 10.6.1 to 10.7.0 in /src/static/app (#990)
Bumps [ol](https://github.com/openlayers/openlayers) from 10.6.1 to 10.7.0.
- [Release notes](https://github.com/openlayers/openlayers/releases)
- [Commits](https://github.com/openlayers/openlayers/compare/v10.6.1...v10.7.0)

---
updated-dependencies:
- dependency-name: ol
  dependency-version: 10.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-05 12:06:28 +01:00
dependabot[bot] 63e6c2040f
build(deps): bump @vue/language-server in /src/static/app (#991)
Bumps [@vue/language-server](https://github.com/vuejs/language-tools/tree/HEAD/packages/language-server) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.1.3/packages/language-server)

---
updated-dependencies:
- dependency-name: "@vue/language-server"
  dependency-version: 3.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-05 12:05:54 +01:00
dependabot[bot] 35de3eb45a
build(deps-dev): bump vite from 7.1.12 to 7.2.2 in /src/static/app (#992)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.12 to 7.2.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.2.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.2.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-05 12:05:44 +01:00
dependabot[bot] 7c35f81af1
build(deps): bump pinia-plugin-persistedstate in /src/static/app (#993)
Bumps [pinia-plugin-persistedstate](https://github.com/prazdevs/pinia-plugin-persistedstate) from 4.5.0 to 4.7.1.
- [Release notes](https://github.com/prazdevs/pinia-plugin-persistedstate/releases)
- [Changelog](https://github.com/prazdevs/pinia-plugin-persistedstate/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prazdevs/pinia-plugin-persistedstate/commits)

---
updated-dependencies:
- dependency-name: pinia-plugin-persistedstate
  dependency-version: 4.7.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-05 12:01:29 +01:00
DaanSelen 1a653bfe32
feat: add custom armv7 compared to amd64 and arm64 (#1007)
* feat: add separate arm/v7 building with c version

* chore: change workflow step names

* fix: my stupid path mistake

* chore: retry sed

* chore: temp comment out

* chore: add libraries and choose for C on arm7

* chore: add arm64 and amd64 back

* chore: streamline workflow

* chore: work on the workflow

* chore: try to separate matrix

* revert to old method with changes

* chore: remove invalid reference

* chore: correct deps

* chore: remove old steps

* feat: add custom armv7 images (compared to arm64 and amd64)

* chore: minor add go version in workflow

---------

Co-authored-by: DaanSelen <dselen@systemec.nl>
2025-12-05 12:00:41 +01:00
Donald Zou 024072688f Stable for SQLite and PostgreSQL 2025-12-02 14:27:51 +08:00
Donald Zou fa59de4a09 Update PeerJobs.py 2025-12-02 14:08:55 +08:00
Donald Zou 4756556c82 Update 2025-12-02 14:00:53 +08:00
Donald Zou 66ffc12864 Update PeerJobs.py 2025-12-02 13:59:48 +08:00
Donald Zou 9f609b01de Update PeerJobs.py 2025-12-02 13:52:57 +08:00
Donald Zou a40df0481f Update 2025-12-02 13:36:45 +08:00
Donald Zou 2de46d97cf Update PeerJobs.py 2025-12-02 13:33:36 +08:00
Donald Zou bd6d27f282 Update 2025-12-02 13:17:24 +08:00
Donald Zou 61c9f5aab7 Update PeerJobLogger.py 2025-12-02 11:34:07 +08:00
Donald Zou aba3c5da64 Added function to remove stale jobs logs 2025-12-02 11:24:03 +08:00
Donald Zou 2ee1b3b138 Manage Tracking Feature Done 2025-12-02 11:00:54 +08:00
Donald Zou 876a079be3 Update 2025-12-01 10:15:59 +08:00
DaanSelen 1fe03e1664 chore: temporary remove arm/v7 for testing 2025-11-26 13:09:01 +01:00
DaanSelen d55770c385 chore: bump psycopg 2025-11-26 12:42:27 +01:00
David Aghaian cd11e0495a
fix: Adds psycopg dependency in order to run properly (#997)
* fix: Adds psycopg dependency in order to run properly

* Update requirements.txt

Remove the redundant dependncy of psycopg, since [binary] package includes it

---------

Co-authored-by: aghaiand <david.aghaian@panasonic.aero>
2025-11-25 15:47:18 +01:00
dependabot[bot] 24367db1f5
build(deps): bump pydantic from 2.12.3 to 2.12.4 in /src (#988)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.12.3 to 2.12.4.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/v2.12.4/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.12.3...v2.12.4)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-version: 2.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-12 11:13:01 +01:00
DaanSelen bfc3e285db
refac: speed up the docker container by removing unneeded steps (#985)
* refac: speed up the docker container by removing unneeded steps

* fix: revert compose tag

---------

Co-authored-by: DaanSelen <dselen@systemec.nl>
2025-11-06 09:31:57 +01:00
Donald Zou 58dc5a70dc
Merge pull request #969 from WGDashboard/Fix-965
Fix #965
2025-11-06 06:36:11 +08:00
dependabot[bot] c94b8a17bc
build(deps): bump @vueuse/shared in /src/static/app (#977)
Bumps [@vueuse/shared](https://github.com/vueuse/vueuse/tree/HEAD/packages/shared) from 13.9.0 to 14.0.0.
- [Release notes](https://github.com/vueuse/vueuse/releases)
- [Commits](https://github.com/vueuse/vueuse/commits/v14.0.0/packages/shared)

---
updated-dependencies:
- dependency-name: "@vueuse/shared"
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 08:55:32 +01:00
dependabot[bot] b18ceca439
build(deps): bump @vuepic/vue-datepicker in /src/static/app (#974)
Bumps [@vuepic/vue-datepicker](https://github.com/Vuepic/vue-datepicker) from 11.0.2 to 11.0.3.
- [Release notes](https://github.com/Vuepic/vue-datepicker/releases)
- [Changelog](https://github.com/Vuepic/vue-datepicker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Vuepic/vue-datepicker/compare/v11.0.2...v11.0.3)

---
updated-dependencies:
- dependency-name: "@vuepic/vue-datepicker"
  dependency-version: 11.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 00:01:23 +01:00
dependabot[bot] b794c342b1
build(deps): bump @vueuse/core from 13.9.0 to 14.0.0 in /src/static/app (#976)
Bumps [@vueuse/core](https://github.com/vueuse/vueuse/tree/HEAD/packages/core) from 13.9.0 to 14.0.0.
- [Release notes](https://github.com/vueuse/vueuse/releases)
- [Commits](https://github.com/vueuse/vueuse/commits/v14.0.0/packages/core)

---
updated-dependencies:
- dependency-name: "@vueuse/core"
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 22:54:25 +01:00
dependabot[bot] 230fa166b8
build(deps-dev): bump vite from 7.1.11 to 7.1.12 in /src/static/app (#975)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.11 to 7.1.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.1.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 22:54:13 +01:00
dependabot[bot] 0ed5640499
build(deps): bump @vue/language-server in /src/static/app (#973)
Bumps [@vue/language-server](https://github.com/vuejs/language-tools/tree/HEAD/packages/language-server) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.1.2/packages/language-server)

---
updated-dependencies:
- dependency-name: "@vue/language-server"
  dependency-version: 3.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 22:52:01 +01:00
dependabot[bot] 931d547046
build(deps): bump psycopg from 3.2.11 to 3.2.12 in /src (#972)
Bumps [psycopg](https://github.com/psycopg/psycopg) from 3.2.11 to 3.2.12.
- [Changelog](https://github.com/psycopg/psycopg/blob/master/docs/news.rst)
- [Commits](https://github.com/psycopg/psycopg/compare/3.2.11...3.2.12)

---
updated-dependencies:
- dependency-name: psycopg
  dependency-version: 3.2.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 22:51:42 +01:00
dependabot[bot] 30dd9f3e54
build(deps): bump psutil from 7.1.1 to 7.1.3 in /src (#983)
Bumps [psutil](https://github.com/giampaolo/psutil) from 7.1.1 to 7.1.3.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-7.1.1...release-7.1.3)

---
updated-dependencies:
- dependency-name: psutil
  dependency-version: 7.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-04 20:16:59 +01:00
Donald Zou c98a83e5b1
Update README with announcements and testing program
Announce WGDashboard's listing on DigitalOcean Marketplace and introduce the WGDashboard Testing Program.
2025-10-28 08:45:16 +08:00
Donald Zou 10c9b5635f Fixed duplicated data 2025-10-24 09:53:40 +08:00
Donald Zou 9dd5341fd9 Remove app access log into the database 2025-10-24 09:53:09 +08:00
Donald Zou d763867566 Fixed stale job issue 2025-10-24 09:21:48 +08:00
Donald Zou 18531b71c7 Fixed schedule task delete issue 2025-10-24 08:47:02 +08:00
dependabot[bot] bbeae93c05
build(deps): bump psycopg from 3.2.10 to 3.2.11 in /src (#968)
Bumps [psycopg](https://github.com/psycopg/psycopg) from 3.2.10 to 3.2.11.
- [Changelog](https://github.com/psycopg/psycopg/blob/master/docs/news.rst)
- [Commits](https://github.com/psycopg/psycopg/compare/3.2.10...3.2.11)

---
updated-dependencies:
- dependency-name: psycopg
  dependency-version: 3.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 08:53:02 +02:00
dependabot[bot] 3c297cbb96
build(deps): bump psutil from 7.1.0 to 7.1.1 in /src (#967)
Bumps [psutil](https://github.com/giampaolo/psutil) from 7.1.0 to 7.1.1.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-7.1.0...release-7.1.1)

---
updated-dependencies:
- dependency-name: psutil
  dependency-version: 7.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 08:52:52 +02:00
dependabot[bot] 954f99b354
build(deps): bump pydantic from 2.12.2 to 2.12.3 in /src (#966)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.12.2 to 2.12.3.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.12.2...v2.12.3)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-version: 2.12.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 08:52:44 +02:00
dependabot[bot] 3f75c0aa3b
Bump @vue/language-server from 3.1.0 to 3.1.1 in /src/static/app (#942)
Bumps [@vue/language-server](https://github.com/vuejs/language-tools/tree/HEAD/packages/language-server) from 3.1.0 to 3.1.1.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.1.1/packages/language-server)

---
updated-dependencies:
- dependency-name: "@vue/language-server"
  dependency-version: 3.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 08:47:10 +02:00
dependabot[bot] 33d8a1f991
Bump npm from 11.6.1 to 11.6.2 in /src/static/app (#953)
Bumps [npm](https://github.com/npm/cli) from 11.6.1 to 11.6.2.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v11.6.1...v11.6.2)

---
updated-dependencies:
- dependency-name: npm
  dependency-version: 11.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 08:46:56 +02:00
dependabot[bot] 616238735a
build(deps-dev): bump vite from 7.1.7 to 7.1.11 in /src/static/app (#963)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.7 to 7.1.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 08:46:43 +02:00
DaanSelen 649c1ad292
feat: add version pinning managed by @dependabot (#960) 2025-10-23 08:46:20 +02:00
dependabot[bot] 145b18c28c
build(deps): bump vue-router from 4.5.1 to 4.6.3 in /src/static/app (#964)
Bumps [vue-router](https://github.com/vuejs/router) from 4.5.1 to 4.6.3.
- [Release notes](https://github.com/vuejs/router/releases)
- [Commits](https://github.com/vuejs/router/compare/v4.5.1...v4.6.3)

---
updated-dependencies:
- dependency-name: vue-router
  dependency-version: 4.6.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 08:46:01 +02:00
dependabot[bot] 34e43de1a1
Bump is-cidr from 5.1.1 to 6.0.1 in /src/static/app (#931)
Bumps [is-cidr](https://github.com/silverwind/is-cidr) from 5.1.1 to 6.0.1.
- [Release notes](https://github.com/silverwind/is-cidr/releases)
- [Commits](https://github.com/silverwind/is-cidr/compare/5.1.1...6.0.1)

---
updated-dependencies:
- dependency-name: is-cidr
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 20:07:01 +02:00
dependabot[bot] b7ed934331
Bump python from 3.13-alpine to 3.14-alpine in /docker (#952)
Bumps python from 3.13-alpine to 3.14-alpine.

---
updated-dependencies:
- dependency-name: python
  dependency-version: 3.14-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 20:06:38 +02:00
Donald Zou d5a7632bb6
Merge pull request #956 from WGDashboard/security-support-patch
chore: update security policy
2025-10-16 11:20:38 +08:00
DaanSelen 6621c5f910
chore: update security policy 2025-10-15 13:44:02 +02:00
DaanSelen 09a3fddd87
fix: alpine error suicide bug (#950)
Co-authored-by: Daan Selen <dselen@systemec.nl>
2025-10-14 08:48:33 +02:00
Donald Zou 45510165a2
Merge pull request #951 from WGDashboard/spel-fix-01
chore: fix spelling errors
2025-10-14 06:19:45 +08:00
DaanSelen 508dbf8dc2
chore: fix spelling errors 2025-10-13 12:34:47 +02:00
Donald Zou eb1d52ffba
Merge pull request #932 from WGDashboard/dependabot/npm_and_yarn/src/static/app/npm-11.6.1
Bump npm from 11.6.0 to 11.6.1 in /src/static/app
2025-10-02 09:35:50 +08:00
Donald Zou 5db7351f8c
Merge pull request #937 from WGDashboard/dependabot/npm_and_yarn/src/static/app/vue/language-server-3.1.0
Bump @vue/language-server from 3.0.8 to 3.1.0 in /src/static/app
2025-10-02 09:35:26 +08:00
dependabot[bot] 5ae3a56337
Bump npm from 11.6.0 to 11.6.1 in /src/static/app
Bumps [npm](https://github.com/npm/cli) from 11.6.0 to 11.6.1.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v11.6.0...v11.6.1)

---
updated-dependencies:
- dependency-name: npm
  dependency-version: 11.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-02 01:35:04 +00:00
Donald Zou e7068b472e
Merge pull request #926 from WGDashboard/dependabot/npm_and_yarn/src/static/app/uuid-13.0.0
Bump uuid from 11.1.0 to 13.0.0 in /src/static/app
2025-10-02 09:33:39 +08:00
dependabot[bot] 3dc94a35a1
Bump @vue/language-server from 3.0.8 to 3.1.0 in /src/static/app
Bumps [@vue/language-server](https://github.com/vuejs/language-tools/tree/HEAD/packages/language-server) from 3.0.8 to 3.1.0.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.1.0/packages/language-server)

---
updated-dependencies:
- dependency-name: "@vue/language-server"
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 23:15:11 +00:00
dependabot[bot] f82abd71a3
Bump uuid from 11.1.0 to 13.0.0 in /src/static/app
Bumps [uuid](https://github.com/uuidjs/uuid) from 11.1.0 to 13.0.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v11.1.0...v13.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-25 22:03:51 +00:00
Donald Zou 602238d794
Merge pull request #933 from WGDashboard/dependabot/npm_and_yarn/src/static/app/vue-3.5.22
Bump vue from 3.5.21 to 3.5.22 in /src/static/app
2025-09-26 06:02:36 +08:00
dependabot[bot] 4d4a15740b
Bump vue from 3.5.21 to 3.5.22 in /src/static/app
Bumps [vue](https://github.com/vuejs/core) from 3.5.21 to 3.5.22.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.21...v3.5.22)

---
updated-dependencies:
- dependency-name: vue
  dependency-version: 3.5.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-25 18:23:15 +00:00
DaanSelen 524d50ee07
Update dependabot.yml
chore: set dependabot to run weekly
2025-09-25 20:21:56 +02:00
dependabot[bot] fc591b7fe8
Bump vite from 7.1.6 to 7.1.7 in /src/static/app (#927)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.6 to 7.1.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-24 10:29:38 +02:00
dependabot[bot] c2f06193d0
Bump @vue/language-server from 3.0.7 to 3.0.8 in /src/static/app (#929)
Bumps [@vue/language-server](https://github.com/vuejs/language-tools/tree/HEAD/packages/language-server) from 3.0.7 to 3.0.8.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.0.8/packages/language-server)

---
updated-dependencies:
- dependency-name: "@vue/language-server"
  dependency-version: 3.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-24 10:29:19 +02:00
Donald Zou f2ead12315
Merge pull request #924 from WGDashboard/fix-#920
Update PeerJobs.py
2025-09-22 22:51:52 +08:00
Donald Zou ca8700ac2a Update PeerJobs.py 2025-09-22 22:50:59 +08:00
Donald Zou 10a8d22efd
Merge pull request #922 from WGDashboard/docker-duplicate-hotfix 2025-09-22 17:53:34 +08:00
Donald Zou fc3ec61373
Merge pull request #923 from WGDashboard/remove-docker-funcs 2025-09-22 05:46:38 +08:00
Daan Selen 094d1c0718 refac: remove docker functions 2025-09-21 22:04:59 +02:00
Daan Selen 0d814ec03c refac: new logic to detecting a Wireguard interface 2025-09-21 21:57:28 +02:00
dependabot[bot] 5ccfe07e12
Bump npm from 10.9.3 to 11.6.0 in /src/static/app (#901)
Bumps [npm](https://github.com/npm/cli) from 10.9.3 to 11.6.0.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v10.9.3...v11.6.0)

---
updated-dependencies:
- dependency-name: npm
  dependency-version: 11.6.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-21 17:30:31 +02:00
dependabot[bot] 101ac5e985
Bump vite from 7.1.5 to 7.1.6 in /src/static/app (#912)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.5 to 7.1.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-19 21:00:15 +02:00
dependabot[bot] 113a780eec
Bump @vue/language-server from 3.0.5 to 3.0.7 in /src/static/app (#902)
Bumps [@vue/language-server](https://github.com/vuejs/language-tools/tree/HEAD/packages/language-server) from 3.0.5 to 3.0.7.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.0.7/packages/language-server)

---
updated-dependencies:
- dependency-name: "@vue/language-server"
  dependency-version: 3.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-19 20:59:48 +02:00
Donald Zou cf77610a56
Merge pull request #914 from WGDashboard/add-template 2025-09-19 18:24:45 +08:00
Daan Selen 84675fe521 feat: add default wg-dashboard.ini config 2025-09-19 10:32:25 +02:00
Donald Zou 5db5b35311
Update README.md 2025-09-17 23:31:20 +08:00
DaanSelen ff345c9609
style: readme update 2025-09-17 16:03:35 +02:00
Daan Selen 6cccfec923 fix: fix the docker building issue
I accidentally removed a character
  we are all human.
2025-09-17 15:58:16 +02:00
Donald Zou 8231dd1463
Merge pull request #906 from WGDashboard/docker-doc-refac
refac(docs): rewrite and check the docker documents
2025-09-17 21:49:25 +08:00
Daan Selen d8ff020d8c refac(docs): rewrite and check the docker documents 2025-09-17 15:34:39 +02:00
Daan Selen 238fb91360 chore: further expand and change the compose file
For people that want to get started quickly
2025-09-17 15:20:01 +02:00
DaanSelen 9ecc16fcc1
chore: update dependabot config 2025-09-17 11:05:31 +02:00
Donald Zou 7d9f60cf9b
Merge pull request #900 from WGDashboard/v4.3.1
Fixed for #843
2025-09-17 16:36:31 +08:00
Donald Zou 8ee16b4173 Fixed for #843 2025-09-17 16:35:57 +08:00
Donald Zou 74861fa96b
Clean up README.md by removing extra line
Removed an extra line before the screenshots section.
2025-09-17 16:10:39 +08:00
Donald Zou 442a658487
Merge pull request #898 from WGDashboard/v4.3.1
V4.3.0.1
2025-09-17 15:25:57 +08:00
Donald Zou 3706b91b26 Build after new package version 2025-09-17 15:23:09 +08:00
Donald Zou 48cb54156b Merge branch 'v4.3.1' of https://github.com/WGDashboard/WGDashboard into v4.3.0.1-dev 2025-09-17 15:19:41 +08:00
Donald Zou f3104c29ea Fixed versions 2025-09-17 15:19:03 +08:00
Donald Zou 689aee34ec
Merge pull request #888 from WGDashboard/dependabot/npm_and_yarn/axios-1.12.2
Bump axios from 1.9.0 to 1.12.2
2025-09-17 15:16:25 +08:00
Donald Zou 3862ea4d28
Merge pull request #887 from WGDashboard/dependabot/npm_and_yarn/pinia-plugin-persistedstate-4.5.0
Bump pinia-plugin-persistedstate from 4.2.0 to 4.5.0
2025-09-17 15:13:41 +08:00
Donald Zou a7e0eb52c2 Bump version 2025-09-17 14:48:11 +08:00
Donald Zou 537a88f618 Fix for #893 2025-09-17 13:06:02 +08:00
Donald Zou 93a5624294 Update dashboard.py
Fix for #892
2025-09-17 11:50:36 +08:00
DaanSelen 37539990dd
Update docker.yml 2025-09-16 16:06:07 +02:00
DaanSelen 5cd8a80118
chore: fix docker scan module 2025-09-16 15:45:22 +02:00
dependabot[bot] c74ecc3d75
Bump axios from 1.9.0 to 1.12.2
Bumps [axios](https://github.com/axios/axios) from 1.9.0 to 1.12.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.9.0...v1.12.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.12.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-16 13:07:33 +00:00
dependabot[bot] 404bccfb64
Bump pinia-plugin-persistedstate from 4.2.0 to 4.5.0
Bumps [pinia-plugin-persistedstate](https://github.com/prazdevs/pinia-plugin-persistedstate) from 4.2.0 to 4.5.0.
- [Release notes](https://github.com/prazdevs/pinia-plugin-persistedstate/releases)
- [Changelog](https://github.com/prazdevs/pinia-plugin-persistedstate/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prazdevs/pinia-plugin-persistedstate/compare/v4.2.0...v4.5.0)

---
updated-dependencies:
- dependency-name: pinia-plugin-persistedstate
  dependency-version: 4.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-16 13:07:26 +00:00
DaanSelen 335c69f517
chore: focus dependabot 2025-09-16 15:06:22 +02:00
DaanSelen 0d118f4d32
Update dependabot.yml 2025-09-16 15:01:01 +02:00
DaanSelen 8f58a06731
testing dependabot 2025-09-16 14:55:32 +02:00
DaanSelen 4bfcc1abde
Create dependabot.yml 2025-09-16 14:51:47 +02:00
Donald Zou 25f39db690
Merge pull request #886 from WGDashboard/DaanSelen-patch-2
Update docker.yml
2025-09-16 20:47:40 +08:00
DaanSelen da5ce7da9e
Update docker.yml 2025-09-16 14:46:51 +02:00
Donald Zou e8126f3630
Merge pull request #885 from WGDashboard/DaanSelen-patch-2
Update docker.yml
2025-09-16 20:26:11 +08:00
DaanSelen f842a7cc62
Update docker.yml 2025-09-16 14:25:40 +02:00
Donald Zou 7250422aaa
Merge pull request #884 from WGDashboard/DaanSelen-patch-1
chore: add rust compiler
2025-09-16 20:00:58 +08:00
DaanSelen 9661ff0b78
chore: add rust compiler 2025-09-16 14:00:20 +02:00
Donald Zou 562201a342
Merge pull request #883 from WGDashboard/v4.3-dev
v4.3.0 Merge
2025-09-16 19:44:49 +08:00
Donald Zou 1ee9f7bc68 Delete Dockerfile-AWG-kernel 2025-09-16 19:14:40 +08:00
Donald Zou 18daa74ecd Merge branch 'main' into v4.3-dev 2025-09-16 19:13:56 +08:00
Donald Zou eadae9373f Merge branch 'main' into v4.3-dev 2025-09-16 19:03:52 +08:00
Donald Zou fb645dd84c Update ko-KR.json 2025-09-16 19:00:52 +08:00
Donald Zou 0cffed3037 Update README.md 2025-09-16 18:58:36 +08:00
Donald Zou 3bd5e02118
Merge pull request #880 from WGDashboard/v4.3-dev-docker
V4.3 dev docker
2025-09-16 16:59:09 +08:00
Donald Zou c4fe81fcbf
Merge pull request #878 from WGDashboard/v4.3-dev-docker-refac
refac: docker entrypoint refactoring (#743) by @AdamGH
2025-09-16 16:58:33 +08:00
Donald Zou 40976870ee Update 2025-09-16 16:49:50 +08:00
Donald Zou d526deb826 Final testing for client side 2025-09-16 14:59:57 +08:00
Donald Zou 4ea3aa0a58 Build 2025-09-16 08:27:44 +08:00
Donald Zou 569ee8ac58 Push 2025-09-16 08:25:32 +08:00
Donald Zou c42776a6d7 Push 2025-09-16 08:14:43 +08:00
Donald Zou 22af38579b Build 2025-09-16 08:01:50 +08:00
Donald Zou a9ecf6c850 Build 2025-09-16 08:01:16 +08:00
Donald Zou 77112675ae Update Peer.py 2025-09-16 07:57:30 +08:00
Donald Zou 0b054ae668 Build Client 2025-09-16 07:47:29 +08:00
Donald Zou 51ba9a86fa Updated AWG 2025-09-16 07:46:25 +08:00
Donald Zou 83eeaa0d73 Push 2025-09-16 02:29:21 +08:00
Donald Zou a2316c8641 Merge branch 'v4.3-dev' of https://github.com/donaldzou/WGDashboard into v4.3-dev 2025-09-15 22:10:13 +08:00
Donald Zou f231e9214c Testing with 2 threads 1 worker 2025-09-15 22:09:57 +08:00
Donald Zou 3673813e6a Cleaned some code files 2025-09-15 21:48:00 +08:00
Donald Zou feb3c38113
Merge pull request #879 from gdeeble/v4.3-dev-fix-empty-expiredate-peersharelink
fix: set date when null expiredate for peersharelink
2025-09-15 19:45:24 +08:00
Daan Selen 73a969a6bf feat: slight refactors to improve logic 2025-09-15 13:20:17 +02:00
Gary Deeble 7ac6d6c498 fix: set date on null expiredate for peersharelink 2025-09-15 06:52:34 -04:00
Adams b2f306789c
Docker entrypoint refactoring (#743)
* Add new env vars for docker

* Add email env vars for docker

* Improve sed for email vars

* Refactor entrypoint.sh file

* Add additional account docker vars

* Add comment for clear command

---------

Co-authored-by: DaanSelen <80752476+DaanSelen@users.noreply.github.com>
2025-09-15 12:09:14 +02:00
Donald Zou 1a26f757a8 Push 2025-09-15 11:41:01 +08:00
Donald Zou 1d66cda277 Push 2025-09-15 11:39:16 +08:00
Donald Zou b52dad961b
Merge pull request #872 from wdk-kr/korean
Update korean translations
2025-09-15 11:35:52 +08:00
Donald Zou 5a84136d87
Merge pull request #877 from gdeeble/v4.3-dev-fix-test-email
Fix: Test email failure
2025-09-15 11:35:30 +08:00
Gary Deeble 73d701eb08 Fix: Add subject variable to allow test email 2025-09-14 14:00:17 -04:00
Donald Zou 636ba5ebc8 Push 2025-09-15 01:28:27 +08:00
Donald Zou 627065e793 Push 2025-09-15 01:06:38 +08:00
Donald Zou c9f395acbd Push 2025-09-15 01:04:56 +08:00
Donald Zou 919a5e9077
Merge pull request #876 from donaldzou/v4.3-dev
V4.3 dev
2025-09-14 23:50:10 +08:00
Donald Zou b5986fde82
Merge branch 'v4.3-dev-docker' into v4.3-dev 2025-09-14 23:50:03 +08:00
Daan Selen cf95aded77 chore: remove mariadb-dev dep 2025-09-14 17:40:53 +02:00
Daan Selen 694a06ddb6 feat: v4.3 docker compatibility 2025-09-14 17:15:41 +02:00
Donald Zou 2eb3a17775 Added "State" status for webhook session 2025-09-14 15:38:19 +08:00
Donald Zou 628464d2e1 Cleanup 2025-09-14 11:01:06 +08:00
Donald Zou ccaaa4bd21 Build of #873 2025-09-13 23:26:23 +08:00
Donald Zou 1b285537ad Fix #873 2025-09-13 23:25:51 +08:00
Donald Zou eab31ba5d0 Update SystemStatus.py 2025-09-13 19:05:32 +08:00
Donald Zou acc1233b11 Update AmneziaWireguardConfiguration.py 2025-09-13 14:49:29 +08:00
Donald Zou 91a3b52a4a Update 2025-09-13 08:23:54 +08:00
Donald Zou b2532e305e Create Dockerfile-Debian-Slim 2025-09-11 20:52:22 +08:00
Donald Zou 06f7e7f74b Update Dockerfile 2025-09-11 20:38:08 +08:00
Donald Zou a517867cdf Update Dockerfile 2025-09-11 20:35:12 +08:00
Donald Zou e1d3ad11cc
Merge pull request #870 from donaldzou/v4.3-dev-docker
feat(docker): prepare the release's Docker deployment
2025-09-11 11:17:00 +08:00
Donald Zou 41dbdc8ccd Update dashboard.py 2025-09-11 11:14:19 +08:00
완두콩 df98ee8738 Update korean translations 2025-09-11 11:57:12 +09:00
Donald Zou 3be1cb6702
Merge pull request #871 from donaldzou/v4.3-dev
Handle backup folder error
2025-09-11 10:39:13 +08:00
Donald Zou 4644840009 Handle backup folder error 2025-09-11 10:38:30 +08:00
Daan Selen 4e75a95a73 chore(docs): add dockerignore 2025-09-10 23:59:58 +02:00
Daan Selen 92a2e26755 feat(docker): clean up remaining commands 2025-09-10 23:59:36 +02:00
Daan Selen 8ebd65cc0b feat(docker): prepare for version 4.3 2025-09-10 20:52:47 +02:00
Donald Zou 15d51735d2 Finished updating locales 2025-09-11 00:16:29 +08:00
Donald Zou ee8afbd357
Merge pull request #868 from donaldzou/main
Keep v4.3 up to date
2025-09-08 17:37:16 +08:00
Donald Zou b3889bb1e3 Feature for #844 2025-09-08 15:12:16 +08:00
Donald Zou 8bbb4a48f7 Update privatePublicKeyInput.vue
Added feature for #835
2025-09-07 22:20:34 +08:00
Donald Zou 2dce79bb85 Added Jinja template for subject #837 2025-09-07 22:12:22 +08:00
Donald Zou 1319c28f90 Added Reset Total Data as a valid peer job task #763 2025-09-07 21:43:49 +08:00
Donald Zou f95c6beeba
Merge pull request #867 from gdeeble/v4.3-dev-no-auth-email
Removed extra logging for email login
2025-09-07 21:11:02 +08:00
Gary Deeble 6cf1eb6140 Removed extra print statement 2025-09-07 08:25:04 -04:00
Donald Zou 4b713ab66e Added authentication option for SMTP
#893 and thank you @gdeeble!
2025-09-07 18:25:48 +08:00
Donald Zou 43d055a8b4 Update WireguardConfiguration.py
Accepted suggestion from #842
2025-09-07 17:44:16 +08:00
Donald Zou 28ce4bb1d6 Build for #525 2025-09-07 17:04:35 +08:00
Donald Zou 604d53d2f0 Added feature #525 2025-09-07 17:04:22 +08:00
Donald Zou 62d3332522 Update WireguardConfiguration.py
Resolved bug where peer is not added when mtu and keepalive is empty
2025-09-07 02:03:24 +08:00
Donald Zou 9caed31185 Added IP logging for #525 2025-09-05 17:57:50 +08:00
Donald Zou 030c1bdcba Added toggle for client side app 2025-09-05 16:39:59 +08:00
Donald Zou 44af7eba11 Finished forgot password for clients app 2025-09-05 15:48:11 +08:00
Donald Zou 41975973dc Build 2025-09-01 22:36:00 +08:00
Donald Zou 572c223854 Adjusted styles 2025-09-01 22:35:43 +08:00
Donald Zou 8191668d60 Build 2025-09-01 20:25:22 +08:00
Donald Zou 632c4f3dc7 Peer history 2025-09-01 17:16:03 +08:00
Donald Zou caacbfae03 Added peer traffic and session 2025-08-29 16:55:33 +08:00
Donald Zou 3b3c071402 Done for #491 2025-08-28 17:58:02 +08:00
Donald Zou 85fa427134 Webhooks feature is done #669 2025-08-28 16:11:01 +08:00
Donald Zou c3c7e50f08 Testing system status and webhook 2025-08-27 19:39:52 +08:00
Donald Zou 45524eaee5 Done #854 2025-08-27 17:48:28 +08:00
Donald Zou 48ec4c7f6f A potential fix for #854
Currently it is showing a sum of all interfaces on sent and receive... not sure if that's right
2025-08-27 11:42:06 +08:00
Donald Zou 593417a1fd
Merge pull request #857 from donaldzou/donaldzou-patch-1
Fix link to WGDashboard DigitalOcean documentation
2025-08-26 14:22:40 +08:00
Donald Zou e2c9941651
Fix link to WGDashboard DigitalOcean documentation
Updated the link for hosting WGDashboard with DigitalOcean. 

Fix #856
2025-08-26 14:14:23 +08:00
Donald Zou f865317600 Finished most of the webhook UI 2025-08-26 00:41:37 +08:00
Donald Zou c83a075886 Update DashboardWebHooks.py 2025-08-25 16:59:57 +08:00
Donald Zou 56d894a8d1 Update DashboardWebHooks.py 2025-08-25 16:54:02 +08:00
Donald Zou bba7817c9b Build 2025-08-25 16:07:17 +08:00
Donald Zou f360ef5d2f Testing webhooks 2025-08-25 16:06:41 +08:00
Donald Zou f6e625c5f8 Webhook UI 2025-08-22 18:26:31 +08:00
Donald Zou e26639cdc4 Adjusted settings page 2025-08-20 21:18:31 +08:00
Donald Zou 38e0a939c2 Added Webhooks Class 2025-08-20 16:47:07 +08:00
Donald Zou 2ec190ecfd Update for plugin 2025-08-20 15:26:07 +08:00
Donald Zou 549982db7f Merge branch 'v4.3-dev' of https://github.com/donaldzou/WGDashboard into v4.3-dev 2025-08-20 15:12:22 +08:00
Donald Zou eab2c9d358 Fixed the issue where Job and Share link is not delete when peer deleted 2025-08-20 15:11:01 +08:00
Donald Zou c5b72cb6d8 Build 2025-08-20 15:11:01 +08:00
Donald Zou 282a83829f Fixed #644 2025-08-20 15:11:01 +08:00
Donald Zou 20edd7cbcd Build 2025-08-20 15:11:01 +08:00
Donald Zou a6311b4f63 Commit for #355 2025-08-20 15:11:01 +08:00
Donald Zou b8792200c6 Peer groups are done... ish? 2025-08-20 15:11:01 +08:00
Donald Zou ba85879151 For now.. 2025-08-20 15:11:01 +08:00
Donald Zou 86bb847374 Build for #843 2025-08-20 15:11:01 +08:00
Donald Zou e61afba608 Completed feature for #843 2025-08-20 15:11:01 +08:00
Donald Zou f418520f66 Build 2025-08-20 15:11:01 +08:00
Donald Zou af3aebe34c Build 2025-08-20 15:11:01 +08:00
Donald Zou 8a568d787a Update client.py 2025-08-20 15:11:01 +08:00
Donald Zou 0d943cb06f Moved all dist code to one folder 2025-08-20 15:11:01 +08:00
Donald Zou f9e9d32c52 Orignal build 2025-08-20 15:11:01 +08:00
Donald Zou 6bf38d4346 Build 2025-08-20 15:11:01 +08:00
Donald Zou 2cd59fc310 Update 2025-08-20 15:10:59 +08:00
Donald Zou 1dfa1d62e1 Build 2025-08-20 15:10:55 +08:00
Donald Zou 854b9d252f Update 2025-08-20 15:10:55 +08:00
Donald Zou 0c0cf3a378 Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou b4f29e63b4 Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou afbb3df571 Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou 01caca707b Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou 8ed5826e6c Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou 2c1e36e54d Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou 1ed8b9f2d5 Build 2025-08-20 15:10:55 +08:00
Donald Zou 0ff8e9ed86 Update 2025-08-20 15:10:55 +08:00
Donald Zou 029081c610 Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou 63b9d15d34 Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou 7dcc3e7589 Update dashboard.py 2025-08-20 15:10:55 +08:00
Donald Zou 81e394a436 Build 2025-08-20 15:10:55 +08:00
Donald Zou 288068bf70 Up 2025-08-20 15:10:55 +08:00
Donald Zou 37546515be Update index.html 2025-08-20 15:10:55 +08:00
Donald Zou c32787ccd3 Update index.html 2025-08-20 15:10:55 +08:00
Donald Zou c9c7084db5 Build 2025-08-20 15:10:55 +08:00
Donald Zou 7b99441602 Build 2025-08-20 15:10:55 +08:00
Donald Zou 24940886f6 Vite build 2025-08-20 15:10:55 +08:00
Donald Zou f777ac5f75 Update protocolBadge.vue 2025-08-20 15:10:55 +08:00
Donald Zou a511eb21fc Completed override peer settings 2025-08-20 15:10:55 +08:00
Donald Zou f11a3c8c3b Update 2025-08-20 15:10:55 +08:00
Donald Zou 430a6053ef Client App 2025-08-20 15:10:55 +08:00
Donald Zou 1867db5c99 Update DashboardPlugins.py 2025-08-20 15:10:55 +08:00
Donald Zou f661cf0f83 Update AmneziaWireguardConfiguration.py 2025-08-20 15:10:55 +08:00
Donald Zou b4814e281f Update WireguardConfiguration.py 2025-08-20 15:10:55 +08:00
Donald Zou 9d4e5d8cb5 Build 2025-08-20 15:10:55 +08:00
Donald Zou 88f40b244a Still need to work on validation 2025-08-20 15:10:55 +08:00
Donald Zou 3e3047f23e Added configuration descriptions 2025-08-20 15:10:55 +08:00
Donald Zou ab4876e066 Added a box-shadow 2025-08-20 15:10:55 +08:00
Donald Zou 00d11880e8 Added WireguardConfigurationInfo 2025-08-20 15:10:55 +08:00
Donald Zou c757cee988 Added Pydantic for configurations info 2025-08-20 15:10:55 +08:00
Donald Zou ebbb681dd8 Delete DashboardPluginsManager.py 2025-08-20 15:10:55 +08:00
Donald Zou feff6ce027 Update Plugins 2025-08-20 15:10:55 +08:00
Donald Zou 39d01015e5 Added plugins manager 2025-08-20 15:10:55 +08:00
Donald Zou 2aa2b15234 Added RRD Tool plugins 2025-08-20 15:10:55 +08:00
Donald Zou 21c6d0b8f9 Migrated configuration sorting to Pinia store
Fixed #841
2025-08-20 15:10:55 +08:00
Donald Zou ffc9176225 Update clientAssignedPeers.vue 2025-08-20 15:10:55 +08:00
Donald Zou d54609cc29 Update client deletion
Optimized client deletion
2025-08-20 15:10:55 +08:00
Donald Zou 0a87453961 Update availablePeersGroup.vue
Added link to each peer so is easier to set schedule jobs and other settings
2025-08-20 15:10:55 +08:00
Donald Zou 12d9058f1e Update DashboardClients.py
Added refreshing clients after signup
2025-08-20 15:10:55 +08:00
Donald Zou f55c961e91 Finished client deletion 2025-08-20 15:10:55 +08:00
Donald Zou fbe4e7dc4c Added router push if received 401 on client side 2025-08-20 15:10:55 +08:00
Donald Zou 53079497a1 Added client profile update 2025-08-20 15:10:55 +08:00
Donald Zou 2d08171e7c Added client profile update 2025-08-20 15:10:55 +08:00
Donald Zou e879ceb1bc Remove deprecated file 2025-08-20 15:10:55 +08:00
Donald Zou 37e2985b9a Build 2025-08-20 15:10:55 +08:00
Donald Zou aaca74874d Update DashboardOIDC.py
Fixed OIDC timeout
2025-08-20 15:10:55 +08:00
Donald Zou 41c5b4bd64 Optimized loading speed for peer information 2025-08-20 15:10:55 +08:00
Donald Zou 71e43eb503 Add client settings 2025-08-20 15:10:55 +08:00
Donald Zou b52bb83c67 Adjusted OIDC template, continue working on building client side app 2025-08-20 15:10:55 +08:00
Donald Zou 7322b7cbf0 Fixed mobile css, sort by local accounts 2025-08-20 15:10:55 +08:00
Donald Zou 62ffd97808 Finished send password reset link 2025-08-20 15:10:55 +08:00
Donald Zou a4ee56648e Added generate reset client password link 2025-08-20 15:10:55 +08:00
Donald Zou f62e481fa0 Fixed email sender validation 2025-08-20 15:10:55 +08:00
Donald Zou fa26fce0cc Merge pull request #820 from ikergcalvino/i18n-update
Standardize locale codes using BCP 47 to improve language support
2025-08-20 15:10:55 +08:00
Iker García Calviño 2fbee4aacc Update localizations with new strings and corrections for improved clarity and consistency 2025-08-20 15:10:54 +08:00
Iker García Calviño 530a7ef393 Add Indonesian, Portuguese, and Hungarian translations, and update supported locales 2025-08-20 15:10:50 +08:00
Iker García Calviño 99cb546b59 Changes to migrate to BCP 47 locale standard 2025-08-20 15:10:48 +08:00
Donald Zou 084bec0f07 Commit 2025-08-20 15:10:13 +08:00
Donald Zou c199413d49 Finished peer assignment 2025-08-20 15:10:13 +08:00
Donald Zou 24eada4432 Build 2025-08-20 15:10:13 +08:00
Donald Zou 4df4aa07f4 Assign peers from client settings is done 2025-08-20 15:10:13 +08:00
Donald Zou 91fd0f0e9a Added peer selections for client 2025-08-20 15:10:13 +08:00
Donald Zou 12f6244930 Fixed MTU and KeepAlive can be empty, rewrote config builder 2025-08-20 15:10:13 +08:00
Donald Zou 327ecbe34c Build for the client manage page 2025-08-20 15:10:13 +08:00
Donald Zou 2ca62293a9 Created clients view for admin 2025-08-20 15:10:13 +08:00
Donald Zou f3cae0b005 Build 2025-08-20 15:10:13 +08:00
Donald Zou 8f15d5dcdd Build 2025-08-20 15:10:12 +08:00
Donald Zou c8348f7be8 Finished client assigning 2025-08-20 15:10:08 +08:00
Donald Zou 1839645360 Peer assignment to client is done 2025-08-20 15:10:04 +08:00
Donald Zou 2c73dc1df8 Update build for client and admin 2025-08-20 15:10:02 +08:00
Donald Zou fefabe073f Updated `psycopg2` to `psycopg` thanks @DaanSelen haha 2025-08-20 15:09:39 +08:00
Donald Zou 77b156c7f5 OIDC should be good to go 2025-08-20 15:09:39 +08:00
Donald Zou 5ac84e109d Update DashboardOIDC.py 2025-08-20 15:09:39 +08:00
Donald Zou 83d105facd Update DashboardOIDC.py 2025-08-20 15:09:39 +08:00
Donald Zou b7af06d59d Update 2025-08-20 15:09:39 +08:00
Donald Zou 4da32690a9 Update 2025-08-20 15:09:39 +08:00
Donald Zou a49c2a1cc0 Update DashboardOIDC.py 2025-08-20 15:09:39 +08:00
Donald Zou f633a9654a Update 2025-08-20 15:09:39 +08:00
Donald Zou 0d58a172a9 Update 2025-08-20 15:09:39 +08:00
Donald Zou 651784b1d1 Update DashboardOIDC.py 2025-08-20 15:09:39 +08:00
Donald Zou 68abc7ec1b Update 2025-08-20 15:09:39 +08:00
Donald Zou 9745e8b034 Update DashboardOIDC.py
Testing more with OIDC
2025-08-20 15:09:39 +08:00
Donald Zou d946c108a3 Update DashboardOIDC.py 2025-08-20 15:09:39 +08:00
Donald Zou 2b66f9a5c4 Update SSL 2025-08-20 15:09:39 +08:00
Donald Zou 4d321cf3f6 Update client.py 2025-08-20 15:09:39 +08:00
Donald Zou 299d84b16a OIDC is ready? I think? 2025-08-20 15:09:39 +08:00
Donald Zou 3d75f6bbbd Spent 4 hours working on OIDC verification 2025-08-20 15:09:39 +08:00
Donald Zou 6b194bba15 Build 2025-08-20 15:09:39 +08:00
Donald Zou e63bccf274 Reconstruct Client App UI 2025-08-20 15:09:39 +08:00
Donald Zou 4ca79ac1c9 Fixed runJob 2025-08-20 15:09:39 +08:00
Donald Zou aafef538f1 Build
# Conflicts:
#	src/static/app/dist/assets/configuration-cdo-S5d9.js
#	src/static/app/dist/assets/dayjs.min-C4BEB1lt.js
#	src/static/app/dist/assets/editConfiguration-UEBqsx1g.js
#	src/static/app/dist/assets/index-CPB-8sFq.js
#	src/static/app/dist/assets/index-CfPziasH.js
#	src/static/app/dist/assets/localeText-CyxQ0Kz3.js
#	src/static/app/dist/assets/message-C4Yp0lwZ.js
#	src/static/app/dist/assets/newConfiguration-ChGJ7xSk.js
#	src/static/app/dist/assets/osmap-C6nMOrBf.js
#	src/static/app/dist/assets/peerAddModal-BmoNcHxr.js
#	src/static/app/dist/assets/peerConfigurationFile-CP8FspMC.js
#	src/static/app/dist/assets/peerJobs-BLwykEew.js
#	src/static/app/dist/assets/peerJobsAllModal-DmXg9VpA.js
#	src/static/app/dist/assets/peerList-sNMkmM1X.js
#	src/static/app/dist/assets/peerSearchBar-CdEZuAVf.js
#	src/static/app/dist/assets/peerSettings-a8GW0whb.js
#	src/static/app/dist/assets/peerShareLinkModal-5XXDseK9.js
#	src/static/app/dist/assets/ping-DuPgpLex.js
#	src/static/app/dist/assets/protocolBadge-BwyLRUKo.js
#	src/static/app/dist/assets/restoreConfiguration-CPPo1_Tp.js
#	src/static/app/dist/assets/schedulePeerJob-DtVhPy4n.js
#	src/static/app/dist/assets/selectPeers-Bs51pfmN.js
#	src/static/app/dist/assets/settings-Dl9arqou.js
#	src/static/app/dist/assets/setup-BzCz1W8G.js
#	src/static/app/dist/assets/share-QVhX-X9Q.js
#	src/static/app/dist/assets/signin-BDhImFq0.js
#	src/static/app/dist/assets/storageMount.vue_vue_type_style_index_0_scoped_9509d7a0_lang-BeoP6Zsu.js
#	src/static/app/dist/assets/systemStatus-_MkxdUbb.js
#	src/static/app/dist/assets/totp-2ovufqb0.js
#	src/static/app/dist/assets/traceroute-BgNVsivF.js
#	src/static/app/dist/assets/vue-datepicker-BZlVex04.js
2025-08-20 15:09:33 +08:00
Donald Zou 68d8546383 Build 2025-08-20 15:09:13 +08:00
Donald Zou c43b3926b8 Added update password in settings 2025-08-20 15:06:27 +08:00
Donald Zou 7e9cfc2872 Update 2025-08-20 15:06:27 +08:00
Donald Zou e88936c05a Update 2025-08-20 15:06:27 +08:00
Donald Zou 9c1b4222d0 Adjusted styles 2025-08-20 15:06:27 +08:00
Donald Zou 69ec55b638 Update 2025-08-20 15:06:27 +08:00
Donald Zou 67a455c403 Update 2025-08-20 15:06:27 +08:00
Donald Zou 5bf4df2d27 Update 2025-08-20 15:06:27 +08:00
Donald Zou 7797cc06d0 Update 2025-08-20 15:06:27 +08:00
Donald Zou 541d89e170 Build 2025-08-20 15:06:27 +08:00
Donald Zou d775fb69e3 Finished tweaking 2FA 2025-08-20 15:06:27 +08:00
Donald Zou 2a1a885056 Finished Index 2025-08-20 15:06:27 +08:00
Donald Zou a334ce1527 Commit 2025-08-20 15:06:27 +08:00
Donald Zou 447cb5ccdc Update DashboardClients.py 2025-08-20 15:06:27 +08:00
Donald Zou bca20e5b02 Update ConnectionString.py 2025-08-20 15:06:27 +08:00
Donald Zou 90675dcc2e Yay! 2025-08-20 15:06:27 +08:00
Donald Zou e8deadaaff Sign In and TOTP is done 2025-08-20 15:06:27 +08:00
Donald Zou ecc4cc7670 Finished SignUp and SignIn frontend and backend 2025-08-20 15:06:27 +08:00
Donald Zou 4a5de5efd4 Added ValidatePasswordStrength in Utilities.py 2025-08-20 15:06:27 +08:00
Donald Zou bdf557fde3 Just commit 2025-08-20 15:06:27 +08:00
Donald Zou 29600cb54c Moved connection string to an individual file 2025-08-20 15:06:27 +08:00
Donald Zou 9f43fd7c92 Reconstruct notification center for client side 2025-08-20 15:06:27 +08:00
Donald Zou 6a6c1aa527 Update 2025-08-20 15:06:27 +08:00
Donald Zou df7f9f2b14 Finished initializing client project 2025-08-20 15:06:27 +08:00
Donald Zou 568da8cc64 Update 2025-08-20 15:06:27 +08:00
Donald Zou e16435f4fc Update 2025-08-20 15:06:27 +08:00
Donald Zou 76e9f3fd29 Update dashboard.py 2025-08-20 15:06:27 +08:00
Donald Zou d0e46a517b Update SQLAlchemy Settings 2025-08-20 15:06:27 +08:00
Donald Zou c94345cb2f Update 2025-08-20 15:06:27 +08:00
Donald Zou e2882acec1 Finally moved all class to its own file 2025-08-20 15:06:27 +08:00
Donald Zou 3c2362177f Refactored `DashboardConfig`
Refactored this file and moved `DashboardConfig` into its own file
2025-08-20 15:06:27 +08:00
Donald Zou 5e92931108 Moved `Utilities.py` into `src/modules` for easier import 2025-08-20 15:06:27 +08:00
Donald Zou d54e388b58 Update DashboardLogger.py
Removed the requirement of using `CONFIGURATION_PATH`
2025-08-20 15:06:27 +08:00
Donald Zou 390cfa0cdf Tested with PostgreSQL and moved PeerJobLogger into its own file 2025-08-20 15:06:27 +08:00
Donald Zou c6fc741aa8 Update dashboard.py 2025-08-20 15:06:27 +08:00
Donald Zou a0e15e1671 Moved PeerJobs to using SQLAlchemy, haven't test PostgreSQL yet 2025-08-20 15:06:27 +08:00
Donald Zou 8367cba259 Moved DashboardAPIKey to its own file 2025-08-20 15:06:27 +08:00
Donald Zou f7bf709295 Moved PeerShareLink and PeerShareLinks to separate file 2025-08-20 15:06:27 +08:00
Donald Zou ab802ea5cf Updated PeerShareLink to use SQLAlchemy 2025-08-20 15:06:27 +08:00
Donald Zou 922d8eab58 Update dashboard.py
- Updated `DashboardConfig` class to use SqlAlchemy, tested with SQLite and Postgresql
2025-08-20 15:06:20 +08:00
Donald Zou 409acc9f1a Updated both logger to use native column type 2025-08-20 15:06:05 +08:00
Donald Zou 196dc78b4f Added support to postgresql and Mysql with SqlAlchemy 2025-08-20 15:06:05 +08:00
Donald Zou 61404d9c12 Replaced both DashboardLogger and PeerJobLogger with SqlAlchemy 2025-08-20 15:06:05 +08:00
Donald Zou ceab5ead8c Fixed the issue where Job and Share link is not delete when peer deleted 2025-08-20 14:15:03 +08:00
Donald Zou 9b60acf3db Build 2025-08-20 01:26:43 +08:00
Donald Zou 90bb321a07 Fixed #644 2025-08-19 19:11:44 +08:00
Donald Zou e56fa24a38 Build 2025-08-19 17:58:54 +08:00
Donald Zou 574aff605f Commit for #355 2025-08-19 17:56:46 +08:00
Donald Zou 9c6d0b56c3 Peer groups are done... ish? 2025-08-19 00:40:01 +08:00
Donald Zou e0761396b8 For now.. 2025-08-17 23:47:21 +08:00
Donald Zou 4b44eb5c80 Build for #843 2025-08-17 19:00:48 +08:00
Donald Zou eb66a44edf Completed feature for #843 2025-08-17 18:58:28 +08:00
Donald Zou f8708b84e6 Build 2025-08-17 17:24:56 +08:00
Donald Zou cc29091116 Build 2025-08-17 17:24:29 +08:00
Donald Zou 2f860772d2 Update client.py 2025-08-17 16:48:31 +08:00
Donald Zou 2f5d1c0966 Moved all dist code to one folder 2025-08-17 16:33:03 +08:00
Donald Zou 39c6817e65 Orignal build 2025-08-17 16:11:03 +08:00
Donald Zou 2d63f56d64 Build 2025-08-17 16:05:04 +08:00
Donald Zou a4a158a9e9 Update 2025-08-17 15:51:39 +08:00
Donald Zou be78cb5321 Build 2025-08-17 15:24:53 +08:00
Donald Zou 1e483dc34d Update 2025-08-17 15:13:30 +08:00
Donald Zou 8ddf77973d Update dashboard.py 2025-08-17 15:09:17 +08:00
Donald Zou d9a4858c4f Update dashboard.py 2025-08-17 15:06:39 +08:00
Donald Zou c3e5406218 Update dashboard.py 2025-08-17 15:04:04 +08:00
Donald Zou b92c345b3a Update dashboard.py 2025-08-17 14:57:20 +08:00
Donald Zou ac9fd8f2ca Update dashboard.py 2025-08-17 02:11:19 +08:00
Donald Zou 20aae4769d Update dashboard.py 2025-08-17 01:49:08 +08:00
Donald Zou 1052c72863 Build 2025-08-16 23:31:54 +08:00
Donald Zou 4beb61c3af Update 2025-08-16 23:19:48 +08:00
Donald Zou 4b6c5db904 Update dashboard.py 2025-08-16 23:06:56 +08:00
Donald Zou 18493bb9b0 Update dashboard.py 2025-08-16 23:04:52 +08:00
Donald Zou 13a4bee725 Update dashboard.py 2025-08-16 22:57:41 +08:00
Donald Zou 7db0f7ec35 Build 2025-08-16 17:21:08 +08:00
Donald Zou 9936038603 Up 2025-08-16 17:20:44 +08:00
Donald Zou ae9fb91c72 Update index.html 2025-08-16 17:05:39 +08:00
Donald Zou dcf7126f51 Update index.html 2025-08-16 17:05:25 +08:00
Donald Zou d3a512bf9e Build 2025-08-16 17:04:36 +08:00
Donald Zou 6809d97dd6 Build 2025-08-16 17:02:21 +08:00
Donald Zou b89919546c Vite build 2025-08-16 16:54:31 +08:00
Donald Zou cfa1c23506 Update protocolBadge.vue 2025-08-16 16:17:25 +08:00
Donald Zou e61b5d2a3f Completed override peer settings 2025-08-16 15:40:57 +08:00
Donald Zou 9089fd37e0 Update 2025-08-16 14:04:12 +08:00
Donald Zou 4eab083a30 Client App 2025-08-16 12:27:50 +08:00
Donald Zou 11a07758aa Update DashboardPlugins.py 2025-08-16 11:19:22 +08:00
Donald Zou ae712c1c98 Update AmneziaWireguardConfiguration.py 2025-08-16 11:17:39 +08:00
Donald Zou 9b2415f0f1 Update WireguardConfiguration.py 2025-08-16 11:12:56 +08:00
Donald Zou f130098937 Build 2025-08-16 11:09:27 +08:00
Donald Zou e280a2e4a9 Still need to work on validation 2025-08-16 01:51:18 +08:00
Donald Zou 60bd4bc91b Added configuration descriptions 2025-08-15 21:45:09 +08:00
Donald Zou 145c3d8f96 Added a box-shadow 2025-08-15 18:28:28 +08:00
Donald Zou 663c134e60 Added WireguardConfigurationInfo 2025-08-15 11:45:06 +08:00
Donald Zou 1e264ca4a1 Added Pydantic for configurations info 2025-08-14 22:36:14 +08:00
Donald Zou 207e9f7afd Delete DashboardPluginsManager.py 2025-08-14 17:23:48 +08:00
Donald Zou 6e4c144af6 Update Plugins 2025-08-14 17:14:52 +08:00
Donald Zou 325c97cfe6 Added plugins manager 2025-08-13 21:41:28 +08:00
Donald Zou 4d07845c7f Added RRD Tool plugins 2025-08-13 16:35:34 +08:00
Donald Zou 93baa505c7 Migrated configuration sorting to Pinia store
Fixed #841
2025-08-12 17:22:40 +08:00
Donald Zou b81d4667b2 Update clientAssignedPeers.vue 2025-08-12 17:21:36 +08:00
Donald Zou 3dd065dd7b Update client deletion
Optimized client deletion
2025-08-12 17:21:08 +08:00
Donald Zou 0a8692dcc0 Update availablePeersGroup.vue
Added link to each peer so is easier to set schedule jobs and other settings
2025-08-12 17:20:18 +08:00
Donald Zou 26cc295167 Update DashboardClients.py
Added refreshing clients after signup
2025-08-12 17:18:31 +08:00
Donald Zou 48d9800b71 Finished client deletion 2025-08-11 17:29:15 +08:00
Donald Zou 9424ad1f13 Added router push if received 401 on client side 2025-08-11 16:32:21 +08:00
Donald Zou 5c58f548c0 Added client profile update 2025-08-11 14:35:54 +08:00
Donald Zou f59111025b Added client profile update 2025-08-11 14:35:30 +08:00
Donald Zou e313776982 Remove deprecated file 2025-08-11 14:35:10 +08:00
Donald Zou faa0bc952f
Merge pull request #845 from alexperreault/main
Update both french translations
2025-08-11 10:37:11 +08:00
Alexandre Perreault 87f8c60e2f Update both french translations 2025-08-10 12:55:20 -04:00
Donald Zou e551c499db Build 2025-08-09 16:46:31 +08:00
Donald Zou 9aaa1edad6 Update DashboardOIDC.py
Fixed OIDC timeout
2025-08-06 17:27:33 +08:00
Donald Zou d96b178a9c Optimized loading speed for peer information 2025-08-06 17:27:14 +08:00
Donald Zou 1c857c0781 Add client settings 2025-08-02 21:58:09 +08:00
Donald Zou ae160aef23 Updated `ConfigParser` to `RawConfigParser`; Updated version number 2025-08-02 16:54:50 +08:00
Donald Zou 2ccce69180 Adjusted OIDC template, continue working on building client side app 2025-08-02 16:51:24 +08:00
Donald Zou a9f618891b Fixed mobile css, sort by local accounts 2025-07-27 02:55:13 +08:00
Donald Zou 85d1cc8be4
Merge pull request #833 from DaanSelen/hinakumo-patch
Hinakumo patch
2025-07-26 02:18:40 +08:00
Donald Zou 6315112b3b Finished send password reset link 2025-07-25 18:06:42 +08:00
Velrino 8cff8d85f6
chore: update documentation links in agentModal and helpModal (#811)
LGTM!
2025-07-24 20:41:00 +02:00
DaanSelen d7a1007f41
Merge branch 'donaldzou:main' into hinakumo-patch 2025-07-24 20:28:20 +02:00
Daan Selen 0cd2c6864e make stale compliant 2025-07-24 20:27:54 +02:00
Daan Selen 0cb46e1444 Docker file update 2025-07-24 20:23:42 +02:00
Donald Zou cb9dfa1321
Fixed Docker system status widget issue (#832)
* Fixed Docker system status widget issue

* Build

ref: https://github.com/donaldzou/WGDashboard/issues/798
2025-07-24 19:42:41 +02:00
Donald Zou 674fea7063 Added generate reset client password link 2025-07-24 23:12:51 +08:00
Donald Zou 722cbb6054 Fixed email sender validation 2025-07-24 23:12:21 +08:00
Donald Zou 042160e6bd Merge pull request #820 from ikergcalvino/i18n-update
Standardize locale codes using BCP 47 to improve language support
2025-07-24 15:05:33 +08:00
Iker García Calviño 06c44fe91f Update localizations with new strings and corrections for improved clarity and consistency 2025-07-24 15:05:32 +08:00
Iker García Calviño d92c636b69 Add Indonesian, Portuguese, and Hungarian translations, and update supported locales 2025-07-24 15:05:32 +08:00
Iker García Calviño 0599503779 Changes to migrate to BCP 47 locale standard 2025-07-24 15:05:32 +08:00
Donald Zou 2cf337a606 Commit 2025-07-24 15:04:46 +08:00
Donald Zou b0bb320fb6 Finished peer assignment 2025-07-22 03:23:58 +08:00
Donald Zou 7a2a2846e1 Build 2025-07-21 17:02:21 +08:00
Donald Zou 90c35b67bd Assign peers from client settings is done 2025-07-21 17:00:33 +08:00
Donald Zou 65287ba800 Added peer selections for client 2025-07-21 02:29:33 +08:00
Donald Zou 14af465aa3 Fixed MTU and KeepAlive can be empty, rewrote config builder 2025-07-20 20:52:37 +08:00
Donald Zou 95f0b60cac Build for the client manage page 2025-07-19 17:18:09 +08:00
Donald Zou d69044231b Created clients view for admin 2025-07-18 21:42:39 +08:00
Donald Zou 72fde9860b Build 2025-07-18 18:50:38 +08:00
Donald Zou 481ada43d6 Build 2025-07-18 18:49:54 +08:00
Donald Zou a9d74e834d Finished client assigning 2025-07-18 18:49:19 +08:00
Donald Zou 43fd2fff2b
Merge pull request #821 from NaturGamerYT/main
Update de-de.json
2025-07-17 13:49:10 +08:00
Florian b37c64f5a5
Update de-de.json 2025-07-16 13:55:28 +02:00
hinakumo f1aa064b2d
Made CDs more direct 2025-07-14 01:26:01 +03:00
Matias G Henschel 8abadd1070
Added PT-BR language, minor ES fix. (#817) 2025-07-13 21:29:43 +02:00
hinakumo 3f9d9732a0
Added build packages deps to Dockerfile 2025-07-12 09:21:29 +03:00
hinakumo b9dc3c44a8
Change AWG repos in Dockerfile 2025-07-12 09:08:10 +03:00
Donald Zou 68e757aafc Peer assignment to client is done 2025-07-10 23:39:21 +08:00
Donald Zou af045447e6 Update build for client and admin 2025-07-08 16:32:08 +08:00
Donald Zou db6976a06a Updated `psycopg2` to `psycopg` thanks @DaanSelen haha 2025-07-08 16:31:03 +08:00
Donald Zou aa66a5ffb2 OIDC should be good to go 2025-07-03 19:20:01 +08:00
Donald Zou bf74150f62 Update DashboardOIDC.py 2025-07-02 19:01:21 +08:00
Donald Zou 2987216169 Update DashboardOIDC.py 2025-07-02 18:59:41 +08:00
Donald Zou 08a41f8f68 Update 2025-07-02 18:57:14 +08:00
Donald Zou 714a824823 Update 2025-07-02 18:56:08 +08:00
Donald Zou 2242dca27d Update DashboardOIDC.py 2025-07-02 18:52:49 +08:00
Donald Zou 681558126d Update 2025-07-02 18:51:58 +08:00
Donald Zou 927e637d88 Update 2025-07-02 18:50:47 +08:00
Donald Zou 3b97cb420d Update DashboardOIDC.py 2025-07-02 18:48:37 +08:00
Donald Zou 241fbd6be5 Update 2025-07-02 18:45:43 +08:00
Donald Zou a619e7f571 Update DashboardOIDC.py
Testing more with OIDC
2025-07-01 13:06:16 +08:00
Donald Zou 491119d676 Update DashboardOIDC.py 2025-06-29 22:27:46 +08:00
Donald Zou 29a8c15d62 Update SSL 2025-06-29 21:18:20 +08:00
Donald Zou 26741512ea Update client.py 2025-06-29 16:14:46 +08:00
Donald Zou a987d91ae1 OIDC is ready? I think? 2025-06-29 16:11:05 +08:00
Donald Zou 380b9a73ab Spent 4 hours working on OIDC verification 2025-06-28 18:13:26 +08:00
Donald Zou 66bd1da571 Build 2025-06-26 17:58:18 +08:00
Donald Zou 79ad3c0a84 Reconstruct Client App UI 2025-06-26 17:56:55 +08:00
Donald Zou e69e7ff3c1 Fixed runJob 2025-06-25 23:16:51 +08:00
DaanSelen 1483ef83d9
Added except label (#806) 2025-06-25 10:51:23 +02:00
Donald Zou dbed799e20
Merge pull request #805 from DaanSelen/staleenglish
English fix
2025-06-25 16:32:28 +08:00
Daan Selen 4602b68425 English fix 2025-06-25 10:27:34 +02:00
Donald Zou 2d3eaedaa7
Update README.md 2025-06-24 17:20:21 +08:00
Donald Zou 4d60b21a5f
Update stale.yml 2025-06-24 17:09:56 +08:00
Donald Zou 50ee8374ee
Update stale.yml 2025-06-24 16:36:22 +08:00
Donald Zou 65eb23e8ce
Update stale.yml 2025-06-24 16:36:13 +08:00
Donald Zou 5c76b18ddd
Delete .github/workflows/stale_action 2025-06-24 16:27:05 +08:00
Donald Zou fc6f5d2535
Create stale.yml 2025-06-24 16:26:35 +08:00
Donald Zou 6a0348e9dc
Create stale_action 2025-06-24 16:15:36 +08:00
Donald Zou 40d3548c82 Build 2025-06-23 16:22:05 +08:00
Donald Zou fc7bbf89c6 Build 2025-06-20 16:01:20 +08:00
Donald Zou 6f848e3df8 Added update password in settings 2025-06-20 16:00:19 +08:00
Donald Zou d80eb03707 Update 2025-06-19 16:51:59 +08:00
Donald Zou 85d4b8c487 Update 2025-06-19 00:41:08 +08:00
Donald Zou 90e6409b1e Update dashboard.py
Update version number
2025-06-17 13:34:13 +08:00
Donald Zou 96b28a8e9b
Update dashboard.py (#791)
Removed `getDashboardConfiguration` from API whitelist
2025-06-16 23:01:04 +02:00
Donald Zou a818e87e96
Merge pull request #793 from DaanSelen/delpr 2025-06-16 22:25:16 +08:00
DaanSelen dc715758a6
Merge branch 'donaldzou:main' into delpr 2025-06-16 14:45:53 +02:00
Daan Selen 87069329d8 Where do these come from? 2025-06-16 14:45:23 +02:00
Daan Selen 8a380a4545 Separated tasks (again) and separate builds 2025-06-16 14:42:39 +02:00
Daan Selen a5e18cb761 del pr running 2025-06-16 14:41:02 +02:00
DaanSelen e9da3e7b6a
streamline docker (#792) 2025-06-16 14:33:16 +02:00
Daan Selen 289fa23728 streamline 2025-06-16 14:27:12 +02:00
Donald Zou c6a44bfe09 Adjusted styles 2025-06-14 19:52:23 +08:00
Donald Zou c6af129960 Update 2025-06-06 15:49:55 +08:00
Donald Zou 6cb30bcd7f Update 2025-06-05 17:57:24 +08:00
Donald Zou 55027fd3cd Update 2025-06-05 17:57:14 +08:00
Donald Zou 41bf9b8baa Update 2025-06-05 15:57:17 +08:00
Donald Zou c117ee61d5
Merge pull request #780 from donaldzou/releasetagging
Docker release management
2025-06-05 10:55:37 +08:00
Daan 7385932e52 donald 2025-06-04 19:54:40 +02:00
Daan cb90b69b3f Looks good! 2025-06-04 19:52:44 +02:00
Donald Zou 8d0e31872a
Update README.md
Testing GitHub Action
2025-06-05 00:18:22 +08:00
Donald Zou 881925fd43
Update docker.yml 2025-06-04 23:52:51 +08:00
Donald Zou 3a2f744f0a
Merge pull request #779 from DaanSelen/scanfix
Add a login to the scan
2025-06-04 23:29:55 +08:00
Daan Selen 9dc9e668c5 Add a login to the scan 2025-06-04 16:53:16 +02:00
Donald Zou 39477c8de8
Merge pull request #778 from donaldzou/DaanSelen-patch-1 2025-06-04 22:44:08 +08:00
DaanSelen 1b0bb95e81
Update docker.yml
I hate this.
2025-06-04 16:41:18 +02:00
Donald Zou cd5a4bec52
Merge pull request #776 from donaldzou/DaanSelen-patch-4 2025-06-04 22:33:26 +08:00
Daan Selen 43070ab809 Syntax fix 2025-06-04 13:14:03 +02:00
Daan Selen 532fedbb62 Adding file 2025-06-04 13:05:35 +02:00
Daan Selen 585bf37783 THIS SHOULD BE WORKING 2025-06-04 13:04:17 +02:00
DaanSelen cad364e407
Update docker-build.yml
Pulled out the action docs this time...
2025-06-04 11:01:38 +02:00
Donald Zou 45457c5b38
Merge pull request #775 from donaldzou/DaanSelen-patch-3 2025-06-04 16:57:28 +08:00
DaanSelen 4e9142b5be
Update docker-build.yml
Its becoming not fun
2025-06-04 10:56:28 +02:00
Donald Zou 207b365d40
Merge pull request #774 from donaldzou/DaanSelen-patch-2 2025-06-04 16:45:00 +08:00
DaanSelen 1ec95a0d86
Update docker-build.yml 2025-06-04 10:42:54 +02:00
Donald Zou db4b9ccc7a
Merge pull request #773 from donaldzou/DaanSelen-patch-1 2025-06-04 16:38:37 +08:00
DaanSelen a86d0c74d3
Update docker-build.yml 2025-06-04 10:36:42 +02:00
Donald Zou 354f4e47df
Merge pull request #772 from DaanSelen/workflowclarify 2025-06-04 16:32:52 +08:00
Daan Selen 84167650b8 hotfix 2025-06-04 10:27:11 +02:00
Daan Selen 15c12a81f1 Separate stages 2025-06-04 10:12:18 +02:00
DaanSelen 249ae584c3
Merge branch 'donaldzou:main' into workflowclarify 2025-06-04 10:09:57 +02:00
DaanSelen b04f7b2d2c
Separated tasks (again) and separate builds (#771)
* Separated tasks (again) and separate builds (#8)

* Update docker-build.yml

Updated `GHCR_TOKEN` to `GITHUB_TOKEN`

---------

Co-authored-by: Donald Zou <donaldzou@live.hk>
2025-06-04 10:01:01 +02:00
Donald Zou 630ce459cb Update docker-build.yml
Updated `GHCR_TOKEN` to `GITHUB_TOKEN`
2025-06-04 15:55:22 +08:00
Donald Zou 68fae3b23c Build 2025-06-03 23:38:10 +08:00
Donald Zou 3525cd1083 Finished tweaking 2FA 2025-06-03 23:37:43 +08:00
DaanSelen a7a30fb282
Separated tasks (again) and separate builds (#8) 2025-06-03 15:19:11 +02:00
Donald Zou e9730f24a0 Finished Index 2025-06-03 17:18:18 +08:00
Donald Zou c35d22a82f Commit 2025-06-03 14:49:56 +08:00
Donald Zou b76d92bfeb Update DashboardClients.py 2025-06-03 14:36:29 +08:00
Donald Zou afa578aa34 Update ConnectionString.py 2025-06-03 12:04:08 +08:00
Donald Zou 519ccda5ed Yay! 2025-06-03 03:11:26 +08:00
Donald Zou 832513a7fc Sign In and TOTP is done 2025-06-03 03:02:06 +08:00
Donald Zou 0300c26952 Finished SignUp and SignIn frontend and backend 2025-06-02 19:23:04 +08:00
Donald Zou 243071d4cc Added ValidatePasswordStrength in Utilities.py 2025-06-02 12:26:28 +08:00
Donald Zou c95937d08b Just commit 2025-06-02 12:04:01 +08:00
Donald Zou 58f944c72e Moved connection string to an individual file 2025-06-01 15:34:12 +08:00
Donald Zou 173cc57490 Reconstruct notification center for client side 2025-06-01 11:19:50 +08:00
Donald Zou 4c8ba6b0a8 Update 2025-05-31 22:59:46 +08:00
Donald Zou ab5abe9bcf
Merge pull request #767 from donaldzou/potential-fix-#762
Background thread terminated when iterating conf list and list changed
2025-05-31 22:52:12 +08:00
Donald Zou be2ea8c6d5 Finished initializing client project 2025-05-31 20:52:53 +08:00
Donald Zou 7834fff541 Update dashboard.py
Potential fix for #762
2025-05-29 16:52:13 +08:00
Donald Zou 16ec9d2938 Update 2025-05-29 16:23:20 +08:00
Donald Zou ef8849e8a9
Update README.md 2025-05-29 00:24:07 +08:00
Donald Zou 93a23671e4 Update 2025-05-27 16:46:44 +08:00
Donald Zou 8a77fbfefd Update dashboard.py 2025-05-26 16:23:06 +08:00
Donald Zou 78bedf9ad6
Update README.md 2025-05-25 12:58:18 +08:00
Donald Zou 6ec757ab66
Merge pull request #759 from donaldzou/fix-pi-os-installation-issue
Update wgd.sh
2025-05-24 20:43:18 +08:00
Donald Zou 9ffb7f54c7 Update SQLAlchemy Settings 2025-05-24 18:25:52 +08:00
Kármán Zsombor 7d71299c51
Add Hungarian language support (#747)
* feat(i18n): add Hungarian (Magyar) language support

* Update active_languages.json

chore: remove extra leading space from line

* Fix: relocate Hungarian (hu-hu) to correct alphabetical position
2025-05-22 23:52:59 +02:00
Donald Zou bea37aee7f Update wgd.sh
Added `raspbian` into installation, thanks @DerPitter bringing this up at #749
2025-05-22 19:52:05 +08:00
Donald Zou 5323687ea5
Update README.md 2025-05-22 17:19:56 +08:00
Donald Zou d1372a4c43
Update README.md 2025-05-22 17:05:25 +08:00
Donald Zou c9249a164a
Merge pull request #758 from DaanSelen/main 2025-05-22 10:00:47 +08:00
DaanSelen 6f105f2626
Updated Dutch translations (#7) 2025-05-21 09:59:40 +02:00
DaanSelen e4c08896f4
Merge branch 'donaldzou:main' into main 2025-05-21 09:50:49 +02:00
Donald Zou e85a0df9b7
Update README.md 2025-05-21 13:08:00 +08:00
Donald Zou a5b7eabd97
Update README.md 2025-05-20 23:56:43 +08:00
Donald Zou f3688431a3
Update README.md 2025-05-20 23:55:34 +08:00
Donald Zou 44e714352d
Merge pull request #755 from donaldzou/fix-awg-qrcode
Fix AmneziaWG QR Code not workiing
2025-05-19 21:55:28 +08:00
Donald Zou 60da68c994 Build 2025-05-19 21:49:19 +08:00
Donald Zou 11288fac20 Update peerQRCode.vue 2025-05-19 21:44:16 +08:00
Donald Zou be10a644a0 Update 2025-05-19 21:43:11 +08:00
Donald Zou a5a64eadc7 Finally moved all class to its own file 2025-05-18 15:24:41 +08:00
Donald Zou 2cee252b14 Refactored `DashboardConfig`
Refactored this file and moved `DashboardConfig` into its own file
2025-05-14 09:24:29 +08:00
Donald Zou 050b4a5c9d Moved `Utilities.py` into `src/modules` for easier import 2025-05-14 09:23:48 +08:00
Donald Zou 6d4b5d4484 Update DashboardLogger.py
Removed the requirement of using `CONFIGURATION_PATH`
2025-05-14 09:22:59 +08:00
Donald Zou 964a6c2e3e Tested with PostgreSQL and moved PeerJobLogger into its own file 2025-05-13 21:36:15 +08:00
Donald Zou fe9d373444
Delete .github/workflows/qodana_code_quality.yml 2025-05-13 20:42:29 +08:00
Donald Zou cce31f9b0b
Merge pull request #744 from donaldzou/fix-schedule-job-issue
Update dashboard.py
2025-05-11 00:14:01 +08:00
Donald Zou ca779ed5ad Update dashboard.py
- Update Job list before every run
2025-05-11 00:09:23 +08:00
Donald Zou 14336529d9 Update dashboard.py 2025-05-11 00:04:37 +08:00
Donald Zou 2e57285120 Moved PeerJobs to using SQLAlchemy, haven't test PostgreSQL yet 2025-05-10 18:16:29 +08:00
Donald Zou 2784059a0f Moved DashboardAPIKey to its own file 2025-05-08 19:05:46 +08:00
Donald Zou 04e78f4de7 Moved PeerShareLink and PeerShareLinks to separate file 2025-05-08 19:03:26 +08:00
Donald Zou c051ab56b4 Updated PeerShareLink to use SQLAlchemy 2025-05-08 17:27:49 +08:00
Donald Zou eb0eaaae2e
Merge pull request #733 from donaldzou/v4.2.3-dev
v4.2.3 Merge
2025-05-07 23:18:33 +08:00
Donald Zou 9631b97694 Final UI Build 2025-05-07 23:15:24 +08:00
Donald Zou 6f036876c8 Doing a final check 2025-05-07 23:14:56 +08:00
Donald Zou bd47179ea4 Build UI for v4.2.3 2025-05-07 22:48:43 +08:00
Donald Zou 17004f704c Update dashboard.py
- Updated `DashboardConfig` class to use SqlAlchemy, tested with SQLite and Postgresql
2025-05-07 18:40:24 +08:00
dselen 418c6bd88b
Merge branch 'donaldzou:main' into main 2025-05-07 08:41:49 +02:00
Donald Zou ada4c4f816 Update dashboard.py
- Fixed #737
- Removed unnecessary code
2025-05-07 13:00:18 +08:00
Donald Zou fc34c1fc35 Updated both logger to use native column type 2025-05-05 20:34:08 +08:00
Donald Zou 6e6cd9a7e5 Added support to postgresql and Mysql with SqlAlchemy 2025-05-05 00:40:29 +08:00
Donald Zou 16051981d7 Replaced both DashboardLogger and PeerJobLogger with SqlAlchemy 2025-05-04 17:44:54 +08:00
Donald Zou 91b499fb14 Update peerQRCode.vue 2025-05-04 17:09:45 +08:00
Donald Zou 1f73adffd6 Fixed issue #698 2025-05-04 01:54:47 +08:00
Donald Zou 82bd313e7a Taking Copilot's suggestion 2025-05-03 17:33:40 +08:00
Donald Zou 14cbfe47b9 Updated theme color 2025-05-03 16:56:20 +08:00
Donald Zou f2d4ff6dc4 Update dashboard.py
Fixed the issue where TOTP Key is not actually reset when reset.
2025-05-03 16:13:30 +08:00
Donald Zou bf33a70727 Updated all language file to latest language template 2025-05-02 23:34:44 +08:00
Donald Zou e1bdcbd581
Merge pull request #708 from Jumala9163/locale/japanese
Updated Japanese translation & Added untranslated parts
2025-05-02 23:00:55 +08:00
Donald Zou 051c1e7622
Merge pull request #722 from reykim7854/locale-indonesia
add Bahasa Indonesia to localization
2025-05-02 22:56:47 +08:00
Donald Zou 3b176474ff
Merge pull request #725 from sf0nt/main
add catalan translation
2025-05-02 22:55:14 +08:00
Donald Zou 15f1b33ea6
Merge pull request #727 from DaanSelen/dockerpidclean
Fix: 723 and more
2025-05-02 22:53:05 +08:00
Donald Zou 0603d4076a
Merge branch 'v4.2.3-dev' into dockerpidclean 2025-05-02 22:52:52 +08:00
reykim7854 ac94f10dc3 fix deprecated locale key 2025-05-02 21:37:55 +07:00
sf0nt bbb92490e9
order languages alphabetically 2025-05-02 16:03:08 +02:00
Donald Zou 2cb63092c0 Fixed AmneziaWG QR Code out of screen on phone 2025-05-02 21:35:27 +08:00
Donald Zou b9bcb59592 Fixed CPU Core floating label height 2025-05-02 20:51:58 +08:00
Donald Zou e083adc022
Merge pull request #728 from BeppeMarnell/#712-peer-delete-bug
API Peer delete bug #712 fix: fixed issue with delete endpoint return…
2025-05-02 20:33:26 +08:00
Donald Zou c3cd38fe9f Update dashboard.py
- Fixed issue where deleting configuration will delete other configuration with similar name
2025-05-02 20:11:11 +08:00
giuseppe a7c2db5e99 API Peer delete bug #712 fix: fixed issue with delete endpoint returning always 200 2025-05-02 11:52:39 +02:00
Daan Selen 4926ee5117 Fix https://github.com/donaldzou/WGDashboard/issues/723 and more 2025-05-02 10:02:38 +02:00
Donald Zou e7723ac3db Build and update version number 2025-05-02 14:34:23 +08:00
Donald Zou 8dbfb93e4e
Merge pull request #726 from donaldzou/fix-awg-advanced-security
Fix awg advanced security
2025-05-02 14:30:57 +08:00
Donald Zou 6096366756
Merge pull request #724 from DaanSelen/main
AmneziaWG Fix for Docker
2025-05-02 14:27:33 +08:00
sf0nt 8e4cf12512
Update active_languages.json 2025-05-02 04:42:43 +02:00
sf0nt b61fa1f870
Rename es-ca.json to ca.json 2025-05-02 04:41:46 +02:00
sf0nt ac3cf9e4b1
Rename es_ca.json to es-ca.json 2025-05-02 04:38:57 +02:00
sf0nt e18463f059
Update es_ca.json 2025-05-02 04:38:19 +02:00
Donald Zou ee54e08d18 Build and removed `advanced_security` from UI 2025-05-01 19:59:58 +02:00
Donald Zou 3c07df6496 Build and removed `advanced_security` from UI 2025-05-01 23:08:40 +08:00
Donald Zou 2117b828c8 Update dashboard.py
Removed `advanced-security` from command line send to AWG
2025-05-01 16:42:37 +02:00
Donald Zou 28d9694432 Update dashboard.py
Removed `advanced-security` from command line send to AWG
2025-05-01 22:40:53 +08:00
Daan Selen 7d977700e6 typo 2025-05-01 16:38:19 +02:00
Donald Zou 33942945d0 commit 2025-05-01 22:28:43 +08:00
Daan Selen 8d7d78db46 Not alpine, latest 2025-05-01 16:21:30 +02:00
Daan Selen 3268cc30ea Donald 2025-05-01 16:20:20 +02:00
Daan Selen 8830ebe34f AmneziaWG Fix for Docker 2025-05-01 16:15:55 +02:00
reykim7854 1d9adba6dd add Bahasa Indonesia to localization 2025-05-01 12:21:29 +07:00
sf0nt 71be73777e
Update es_ca.json 2025-05-01 02:51:53 +02:00
sf0nt 7709f70ef1
Update es_ca.json 2025-05-01 02:21:21 +02:00
sf0nt 9b528b84e1
Update es_ca.json 2025-05-01 02:09:04 +02:00
sf0nt c53a4d4861
Update es_ca.json 2025-04-30 05:23:02 +02:00
sf0nt 766173df3d
Update es_ca.json 2025-04-30 05:02:43 +02:00
sf0nt 7f65cae891
Update es_ca.json 2025-04-30 04:43:41 +02:00
sf0nt bc56ecb85c
Update es_ca.json 2025-04-30 03:59:00 +02:00
sf0nt 50c3151301
Update es_ca.json 2025-04-30 03:41:49 +02:00
sf0nt d49ec0a81e
Update es_ca.json 2025-04-30 03:07:00 +02:00
sf0nt 6692028762
Update es_ca.json 2025-04-30 02:49:00 +02:00
sf0nt b71c357958
Create es_ca.json 2025-04-30 02:40:47 +02:00
sf0nt 03b7621f3e
Update active_languages.json with catalan 2025-04-30 02:40:01 +02:00
Donald Zou 2bcf24bd84
Merge pull request #710 from reloadlife/feat/route-table
feat: Supporting "table" directive in the configuration
2025-04-27 16:17:17 +08:00
Jumala9163 cc5aa05b12
Fixed where translations were not being done correctly. 2025-04-26 17:26:23 +09:00
Jumala9163 3232c5c4ce
update ja-jp & Add new locations that are not yet translated 2025-04-26 16:50:31 +09:00
dselen 72a52f5cd6
Merge pull request #704 from DaanSelen/dockerdoc
Docker files cleanup
2025-04-25 14:58:35 +02:00
Daan Selen bda48a56e0 Docker files cleanup 2025-04-25 09:27:07 +02:00
Donald Zou 09cdcf8e53 Update version number 2025-04-25 14:23:51 +08:00
Donald Zou a4d5b41ca7
Merge pull request #703 from donaldzou/fix-#702
Fix #702 and #699
2025-04-25 14:15:33 +08:00
Donald Zou 9fa0d91d06 Update Email.py
Fixed #699
2025-04-25 14:08:45 +08:00
Donald Zou 510f60bdeb Update Email.py
Fixed to accomodate SMTP service where `username` is not an email address from #702
2025-04-25 13:39:29 +08:00
Mohammad Mahdi Afshar 30fe827253 feat: Supporting "table" directive in the configuration 2025-04-24 18:51:26 +03:30
Donald Zou 8f0f4b168b Update README.md
Added demo server
2025-04-24 19:13:34 +08:00
Donald Zou cd11c4beb6 Merge branch 'main' of https://github.com/donaldzou/WGDashboard 2025-04-23 21:27:01 +08:00
Donald Zou 4d49cc413a Update README.md 2025-04-23 21:26:21 +08:00
Donald Zou 3d50a58a31
Merge pull request #690 from donaldzou/donaldzou-patch-2
Update README.md
2025-04-23 20:23:03 +08:00
Donald Zou d5701230fa Pushing this for Docker 2025-04-23 20:22:26 +08:00
Donald Zou ab945d6afe
Update README.md
Changed the icon!
2025-04-23 20:06:44 +08:00
Donald Zou b4f8a36d43
Merge pull request #689 from donaldzou/fix-#683
Fix #683
2025-04-23 19:31:50 +08:00
Donald Zou 608c1b4eb6 Update version number to v4.2.1 2025-04-23 19:30:41 +08:00
Donald Zou edf3c42157 Update version number 2025-04-23 19:26:31 +08:00
Donald Zou c523cec113 Fixed #683 and Re-build 2025-04-23 19:24:50 +08:00
Donald Zou 6f8b987d42
Merge pull request #688 from donaldzou/fix-welcome-session-issue
Update dashboard.py
2025-04-23 18:06:10 +08:00
Donald Zou d0d0642bdf Update dashboard.py
Fixed issue where new user can't finish welcome session
2025-04-23 18:05:19 +08:00
Donald Zou 924d760e3b
Merge pull request #687 from DaanSelen/delarmv6
Remove arm v6 because go image has not been built for it.
2025-04-23 16:29:17 +08:00
Donald Zou f8c207ca2b
Merge pull request #685 from Jorropo/fr-fr
locale: Add fr-fr language
2025-04-23 16:22:43 +08:00
Daan Selen ada1edd0b7 Remove arm v6 because go image has not been built for it. 2025-04-23 10:08:09 +02:00
Donald Zou c79333db61
Merge pull request #681 from DaanSelen/imagebump
Imagebump
2025-04-23 16:00:53 +08:00
Jorropo 13778bed87 locale: Add fr-fr language
This file copies most of the work from @alexp did in af5e7974 when adding fr-ca language.

There a handful of sentences being rephrased.
Typographic changes.
And few corrections that should probably be done in fr-ca too.
2025-04-23 07:33:16 +02:00
Daan 83b4d96f42 go image bump 2025-04-22 21:36:28 +02:00
Daan 6cf96de0b4 Moved Docker file around, cleaning. 2025-04-22 21:20:41 +02:00
Donald Zou 481fadc7fc Build 2025-04-23 02:15:05 +08:00
Donald Zou 44f2c59e56
Merge pull request #679 from donaldzou/v4.2-dev
V4.2 dev
2025-04-23 02:13:39 +08:00
Donald Zou 83589a912f Update README.md 2025-04-22 22:34:09 +08:00
Donald Zou fab9f03e7d Update README.md 2025-04-22 22:33:22 +08:00
Donald Zou 928c83b13c Update README.md 2025-04-22 22:26:29 +08:00
Donald Zou 72f20bc69b Update README.md 2025-04-22 22:26:01 +08:00
Donald Zou 8293d5379d Update README.md 2025-04-22 22:23:11 +08:00
Donald Zou 1d29445a8b Merge branch 'v4.2-dev' of https://github.com/donaldzou/WGDashboard into v4.2-dev 2025-04-22 18:42:22 +08:00
Donald Zou 5c308d757f Update wgd.sh and removed SSL-TLS from Gunicorn 2025-04-22 18:42:08 +08:00
Donald Zou 5bf16004c9
Merge branch 'main' into v4.2-dev 2025-04-21 18:11:09 +08:00
Donald Zou 417fa437b7 Update 2025-04-21 17:25:12 +08:00
Donald Zou 43f893c921 Update 2025-04-21 15:59:42 +08:00
Donald Zou 8ad9af4bc2 Added Belarusian to v4.2 2025-04-20 15:47:24 +08:00
Donald Zou b3931bdc9d
Merge pull request #646 from Bardatsky/main
added Belarusian language
2025-04-20 15:46:27 +08:00
Donald Zou 7ac153848b
Merge branch 'main' into main 2025-04-20 15:45:18 +08:00
Donald Zou 7689ebcac5 Update 2025-04-20 02:52:22 +08:00
Donald Zou bf7adf9ca9 Build 2025-04-20 02:46:32 +08:00
Donald Zou 51ab8c556a Sorting language available automatically 2025-04-20 02:40:36 +08:00
Donald Zou 149a8e910d Merged latest locale from main 2025-04-20 02:37:54 +08:00
Donald Zou 310746b8cd
Merge pull request #676 from donaldzou/DaanSelen-patch-1
Update active_languages.json
2025-04-19 22:26:28 +08:00
Donald Zou 6681303450 Adjusted some API endpoint 2025-04-19 02:54:47 +08:00
dselen 3228f37f09
Merge branch 'main' into DaanSelen-patch-1 2025-04-17 23:04:26 +02:00
dselen 421785bf6a
Merge pull request #675 from mahdiMGF2/main
added Farsi language
2025-04-17 23:01:50 +02:00
Mahdi 308e8ca8c7 The Persian language was organized based on the alphabet. 2025-04-13 15:14:01 +03:30
Daan cb068ef70e Reordering on lang_name 2025-04-13 12:02:35 +02:00
dselen acd3ec782f
Update active_languages.json
Alphabetical
2025-04-13 11:40:14 +02:00
Mahdi 12d1e5b8d0 added Farsi language 2025-04-11 09:04:02 +03:30
dselen b165cdbd79
Merge pull request #672 from Jumala9163/jumala9163/locale/japanese
Add Japanese Language
2025-04-07 16:05:17 +02:00
Donald Zou 66f4507dee Refactored some API endpoint 2025-04-07 18:45:12 +08:00
Donald Zou a724e1cb58
Update README.md 2025-04-07 17:41:12 +08:00
Donald Zou f942809de0
Update README.md
Added DO as sponsor!
2025-04-07 15:14:07 +08:00
Donald Zou 8043f77e02
Update README.md 2025-04-06 05:32:15 +08:00
Jumala9163 1db9ba90d8
fix typo 2025-04-06 00:23:30 +09:00
Jumala9163 464fa59cb6
Add Japanese(ja-jp) Language 2025-04-06 00:12:00 +09:00
Donald Zou a8e2cbf55b
Update README.md 2025-04-05 22:38:20 +08:00
Donald Zou fecd8dab38
Update README.md 2025-04-05 20:45:37 +08:00
Donald Zou 13a833e3ed
Update README.md 2025-04-04 16:53:17 +08:00
Donald Zou 8d4784052a Build v4.2 2025-04-03 23:03:00 +08:00
Donald Zou d9e9b41861 Build 2025-03-28 00:13:54 +08:00
Donald Zou f0c3ef0aa1 Added Chatbot to the App! 2025-03-28 00:13:38 +08:00
Donald Zou 3888831679
Merge pull request #663 from donaldzou/DaanSelen-patch-3
Update docker-related.yaml
2025-03-26 17:11:02 +08:00
dselen 1a32fad324
Update docker-related.yaml
Added nigthly trigger.
2025-03-26 09:42:11 +01:00
Donald Zou d842ae9540
Update docker-related.yaml 2025-03-25 15:36:53 +08:00
Donald Zou d0177b7504
Update docker-related.yaml 2025-03-25 15:17:41 +08:00
Donald Zou e3842b25f3
Update docker-related.yaml 2025-03-25 15:11:06 +08:00
Donald Zou a29b59c9cd
Update docker-related.yaml 2025-03-24 17:36:05 +08:00
Donald Zou f94eb97aa4
Update docker-related.yaml 2025-03-24 12:52:28 +08:00
Donald Zou 86017b79eb
Update docker-related.yaml 2025-03-24 12:50:26 +08:00
Donald Zou bc22fa5fad
Update docker-related.yaml 2025-03-24 12:42:03 +08:00
Donald Zou d6b70028ff
Merge pull request #662 from donaldzou/donaldzou-patch-1
Update docker-related.yaml
2025-03-24 05:11:36 +08:00
Donald Zou 0f7f9acd58
Update docker-related.yaml 2025-03-24 05:11:22 +08:00
Donald Zou 20633a6d1a
Update docker-related.yaml 2025-03-24 05:00:45 +08:00
Donald Zou a23856270d
Merge pull request #661 from donaldzou/DaanSelen-patch-2
Update docker-related.yaml
2025-03-24 04:27:53 +08:00
dselen 55543e370e
Update docker-related.yaml
Workflow fix fix
2025-03-23 21:20:40 +01:00
Donald Zou 8137a46c68
Merge pull request #660 from DaanSelen/workflow-fix
Workflow fix.
2025-03-24 04:16:34 +08:00
Daan aeb9597c71 Workflow fix. 2025-03-23 20:41:48 +01:00
Donald Zou 5067485e94 Commit 2025-03-16 00:42:40 +08:00
Donald Zou dfd456c7dc Update qodana_code_quality.yml 2025-03-12 01:01:42 +08:00
Donald Zou 0ccb07e683 Push 2025-03-12 00:59:32 +08:00
Donald Zou ca67a6897f Update 2025-03-12 00:44:36 +08:00
Donald Zou 0390227641
Update qodana_code_quality.yml 2025-03-12 00:36:07 +08:00
Donald Zou 395b0982db
Merge pull request #650 from donaldzou/qodana-automation-897468090
Add qodana CI checks
2025-03-11 18:00:52 +08:00
Qodana Application f096ab4da7 Add github workflow file 2025-03-11 10:00:39 +00:00
Qodana Application d5ec9f7640 Add qodana.yaml file 2025-03-11 10:00:39 +00:00
Donald Zou 5732867407 Fixed #493 2025-03-11 17:52:53 +08:00
Егор Бардацкиий 31842f4c12 added Belarusian language 2025-03-08 03:45:26 +03:00
Donald Zou d5168d2da6 Update dashboard.py 2025-03-07 00:52:51 +08:00
Donald Zou cac5ec836b Update dashboard.py
- Fixing #601
2025-03-07 00:45:01 +08:00
Donald Zou ee9569e7d4 Update dashboard.py
- Trying to fix #601
2025-03-07 00:37:01 +08:00
dselen 95df7de026
Merge pull request #639 from karorogunso/main
Add Thai language
2025-02-27 11:51:53 +01:00
Karorogunso d3a5bd374d
Merge pull request #1 from karorogunso/thai
Add Thai to main brach
2025-02-27 16:44:42 +07:00
Karorogunso 43ac3dddf1
Merge branch 'donaldzou:main' into thai 2025-02-27 16:25:57 +07:00
Karorogunso 1174328de3
Update active_languages.json 2025-02-27 16:24:28 +07:00
dselen dda54fb907
Merge pull request #625 from DaanSelen/arch-support
Arch Linux Support
2025-02-20 13:14:35 +01:00
Daan 92ea808a5d Arch Linux Support 2025-02-19 18:49:00 +01:00
dselen 2bc3a75c94
Merge pull request #621 from wdk-kr/korean
Add Korean Language
2025-02-18 13:04:56 +01:00
완두콩 07ef97ce7c Update active_languages.json 2025-02-18 20:13:52 +09:00
완두콩 5fe3539331 Update ko.json 2025-02-18 15:52:50 +09:00
완두콩 4bc3bd5f13 Update ko.json 2025-02-18 15:51:24 +09:00
완두콩 4abce854d7 Update active_languages.json 2025-02-18 15:36:01 +09:00
완두콩 3b5c73992e Fix incorrect translations 2025-02-18 15:25:10 +09:00
완두콩 c5abea2944 Add Korean Language 2025-02-18 15:11:08 +09:00
Donald Zou 8e5cf14ebc
Merge pull request #619 from akhepcat/v4.2-dev
Update wgd.sh - install pypi mirror can auto select by rtt after delay
2025-02-18 02:46:19 +08:00
Leif 7c8410ab86
Update wgd.sh
update msleep for longer timeout, and move that var to the top to make it more obvious
2025-02-17 09:41:11 -09:00
Donald Zou 369b4b92cc Update dashboard.py
- Fixed #
2025-02-17 15:25:33 +08:00
Donald Zou c50bb70383 Build 2025-02-17 14:25:49 +08:00
Donald Zou d84b2060f0 Added endpoint back when on list 2025-02-17 14:25:37 +08:00
Donald Zou afcf6024e6 Update 2025-02-17 14:01:56 +08:00
Donald Zou 3542bd6668 Adjusted UI to fit list display 2025-02-17 13:47:05 +08:00
Leif Sawyer 69b9116dd5 Add auto-select based on ping response 2025-02-16 14:53:03 -09:00
Leif a2db4f06b1
Update wgd.sh install pypi mirror auto select
add timeout value so that the install/update can run fully scripted without normal user intervention if desired
2025-02-16 11:43:47 -09:00
Donald Zou b60b0fb511 Resolved #596 2025-02-17 00:20:36 +08:00
Donald Zou 0c1e9a6bb5 Fixed #499 2025-02-16 23:08:24 +08:00
Donald Zou 2692f92cb9 Fixed #609 2025-02-16 23:07:16 +08:00
Donald Zou a43c8b4b00 Update dashboard.py 2025-02-16 17:43:22 +08:00
Donald Zou 6f15389411 Updated how available IP is generated 2025-02-16 17:42:32 +08:00
Donald Zou f055241802 Build 2025-02-15 16:50:09 +08:00
Donald Zou ac77c3a390 Fixed hardcoded issue 2025-02-15 16:49:49 +08:00
Donald Zou 5cd99f2edc Update dashboard.py
Fixed #490
2025-02-14 23:59:21 +08:00
Donald Zou 7c70fbec30 Build 2025-02-14 23:24:16 +08:00
Donald Zou ac0dc3196f
Merge pull request #612 from akhepcat/v4.2-dev
Fix version checking
2025-02-14 16:07:18 +08:00
Leif 7f4da826b1
Fix version checking
don't just check for string differences, but compare the version numbering using packaging.version
2025-02-13 23:04:27 -09:00
Donald Zou cc2af4371f Build 2025-02-12 20:07:37 +08:00
Donald Zou 2a79a03d38 Build 2025-02-10 16:17:15 +08:00
Donald Zou 47aac7fe33 Added automatically assign keys and allowed ip when none provided
Resolve #515
2025-02-10 16:17:00 +08:00
Donald Zou d4055884b1 Fixed #580 2025-02-08 16:40:20 +08:00
Donald Zou 61658e847a Merge branch 'v4.2-dev' of https://github.com/donaldzou/WGDashboard into v4.2-dev 2025-02-08 15:45:40 +08:00
Donald Zou bd714223ce Update dashboard.py 2025-02-08 15:45:09 +08:00
dselen 060154cb89
Merge pull request #595 from sobhydo/main
Adding Arabic Translation
2025-02-06 14:13:23 +01:00
dselen 99a1bfca9d
Update active_languages.json
removed leading newline
2025-02-06 14:10:10 +01:00
dselen 4379f30628
Update active_languages.json
removed trailing enter.
2025-02-06 14:09:47 +01:00
Ahmed Sobhy b501244577 Alphabetical order for active languages 2025-02-06 16:53:43 +04:00
Daan Selen 6f7b9815ca Hotfix for stop entrapment and addition of workdir in Dockerfile 2025-02-06 11:41:41 +01:00
dselen 227bd088f7
Merge pull request #603 from DaanSelen/4.2
Docker enhancements.
2025-02-06 10:56:32 +01:00
Daan Selen b1d6ecb07c Tidied up compose 2025-02-06 10:55:07 +01:00
Daan Selen 41772f28bd Added a SIGTERM trap to the entrypoint script for faster stopping. 2025-02-06 10:52:14 +01:00
Daan Selen 393dac1c99 Working https://github.com/donaldzou/WGDashboard/issues/586 2025-02-05 10:32:36 +01:00
Daan db9d0be6c7 Updated docker configuration. 2025-02-02 23:24:21 +01:00
Ahmed Sobhy f0774ec273 Adding Arabic Translation 2025-01-27 23:41:06 +04:00
karorogunso d4a4d28b58 Add Thai Language 2025-01-27 21:39:06 +07:00
Donald Zou 3ec97021bd Build 2025-01-26 23:51:00 +08:00
Donald Zou 56cf972373 Build 2025-01-26 23:09:09 +08:00
Donald Zou 2f3ae4c1af Pushi 2025-01-26 23:07:32 +08:00
Donald Zou bc3dd04e12 Push 2025-01-26 23:07:19 +08:00
Donald Zou 4e8fc1b431 Commit 2025-01-26 23:03:09 +08:00
Donald Zou cd39aa2968 Update 2025-01-25 22:15:19 +08:00
Donald Zou 87ea3fc982 Update PeerJobLogger.py 2025-01-25 22:11:50 +08:00
Donald Zou f74a511778 Update PeerJobLogger.py 2025-01-25 22:10:29 +08:00
Donald Zou 202461fe48 Update PeerJobLogger.py 2025-01-25 21:56:29 +08:00
Donald Zou 2af6687351 Build 2025-01-24 19:19:51 +08:00
Donald Zou e603af5f24 Refactored system status into a class. Added charts 2025-01-24 19:19:17 +08:00
Donald Zou 84069ee882 Refactor some new code 2025-01-24 00:01:29 +08:00
Donald Zou edbb5cef92 Refactor some code 2025-01-22 15:46:04 +08:00
Donald Zou a62c54b4ed
Merge pull request #576 from petrsimunek/patch-1
Update cs.json
2025-01-19 21:07:40 +08:00
Donald Zou 41df7c04c3
Merge pull request #585 from donaldzou/fix-#581
Ready for update
2025-01-19 21:03:38 +08:00
Donald Zou 9b783a8322 Ready for update 2025-01-19 21:03:08 +08:00
Donald Zou 57db4df618
Merge pull request #584 from donaldzou/fix-#581
Fixed Job Logger Bug, Restrict Peers with Configuration include special characters
2025-01-19 20:57:04 +08:00
Donald Zou 9d1081bd56 Update dashboard.py
- Fixed #581 issue.
2025-01-19 20:54:19 +08:00
Donald Zou cc3773817b Refactor 2025-01-19 20:52:04 +08:00
Donald Zou 8956355e57 Update dashboard.py
Removed the need to check for each custom classes, will check if the class have `toJson`, if `True` then use it.
2025-01-19 14:04:59 +08:00
Donald Zou 645db97c14 Refactored some custom classess 2025-01-19 14:04:21 +08:00
Donald Zou 07a04dc507 Update dashboard.py 2025-01-19 13:20:04 +08:00
Donald Zou a7317af413 Update code for #446 2025-01-16 18:36:06 +08:00
Donald Zou ba6d6b8851 Display configuration as grid, system status process bug 2025-01-16 15:40:33 +08:00
Donald Zou 3726810108 Build with email functions 2025-01-16 00:46:38 +08:00
Donald Zou 4f92a7edf3 Finalized the email function 2025-01-16 00:44:22 +08:00
Donald Zou 5d84b61f18 Build with Email enabled 2025-01-13 16:47:15 +08:00
Donald Zou cd1329ec67
Update README.md 2025-01-13 01:41:25 +08:00
petrsimunek 48a58b2b69
Update cs.json
Added translation for dashboard IP and port settings
2025-01-11 16:23:29 +01:00
Donald Zou 9b64aba8bf
Merge pull request #575 from mahdiMGF2/main
Fixed some syntax error in Peer Jobs
2025-01-09 17:21:22 +08:00
Donald Zou cae8264d98
Update dashboard.py 2025-01-09 17:13:32 +08:00
Mahdi 95d8985336 Bug fixed
Bug Fixed deletePeerScheduleJob endpoint
2025-01-09 11:38:45 +03:30
Donald Zou b26ae90807
Merge pull request #574 from donaldzou/v4.1.3-dev
v4.1.3 Ready
2025-01-09 14:02:19 +08:00
Donald Zou 66d171c432 v4.1.3 Ready 2025-01-09 13:42:21 +08:00
Donald Zou 40463d9831 Email functionality is done 2025-01-08 18:09:05 +08:00
Donald Zou eb7dee013d Refactored add peers 2025-01-05 14:32:11 +08:00
dselen 1a878599b1
Merge pull request #549 from alexperreault/main
French translation
2025-01-03 08:39:41 +01:00
Donald Zou b9e25abdd9 Refactored peer list 2025-01-03 13:45:05 +08:00
alexp c612022717 alphabetical order 2025-01-02 20:23:52 -05:00
Donald Zou 31e7f02b8d Still trying 2025-01-02 11:38:21 +08:00
Donald Zou 75747a2979
Merge pull request #568 from donaldzou/fix-backup-folder-not-created
Create WGDashboard_Backup when configuration initialized
2025-01-02 11:36:11 +08:00
Donald Zou a85a8668a7 Update dashboard.py 2025-01-01 10:36:44 +08:00
Donald Zou cca5fd859c Update dashboard.py
- Create WGDashboard_Backup
2024-12-31 11:02:52 +08:00
Donald Zou 2ed49abb1b Update dashboard.py
Minor issue
2024-12-31 10:44:27 +08:00
Donald Zou 409e6d49b2 Update dashboard.py
- Fixed `WGDashboard_Backup` not being created and caused crash
2024-12-31 10:33:28 +08:00
Donald Zou 93cfc482b8 Commit 2024-12-31 10:31:24 +08:00
Donald Zou 85be6d53d0
Merge pull request #566 from donaldzou/fix-edit-configuration
Edited configuration not updating to dashboard
2024-12-30 20:33:10 +08:00
Donald Zou bf6f58eb5e Remove `with sqldb` in SQL Select 2024-12-30 20:31:24 +08:00
Donald Zou cd9d17ab18 Refactoring configuration list 2024-12-30 20:30:09 +08:00
Donald Zou e0bc6a10d0 Edited configuration not updating to dashboard 2024-12-29 16:37:19 +08:00
Donald Zou ccfc1ad166 Update dashboard.py 2024-12-29 15:57:57 +08:00
Donald Zou 812060240f Update dashboard.py 2024-12-29 13:12:40 +08:00
Donald Zou 715a266ca4
Merge pull request #563 from donaldzou/fix-#562
Fix #562, and mysterious Jobs disappear issue
2024-12-28 23:53:35 +08:00
Donald Zou fa4b3ece56 Log the error instead of remove the job 2024-12-28 21:01:21 +08:00
Donald Zou aac2a002bb
Delete .github/workflows/main.yml 2024-12-28 01:34:33 +08:00
Donald Zou 8574acaf6e
Create main.yml 2024-12-28 01:27:25 +08:00
Donald Zou ddf4639354
Update README.md 2024-12-28 01:26:47 +08:00
Donald Zou 9bd394f351 Update dashboard.py 2024-12-27 03:09:27 +08:00
Donald Zou 6899d48aae Update 2024-12-26 00:06:37 +08:00
Donald Zou 514e1ca8d0 Build 2024-12-18 18:15:02 +08:00
Donald Zou eba5be010a Update 2024-12-18 17:45:22 +08:00
Donald Zou f578d5c1c9 Enhanced peer list rendering
- Used IntersectionObserver to better render peer list, in case of too many peers will slow down browser
2024-12-18 15:06:41 +08:00
Donald Zou e58b1d670b
Update README.md 2024-12-15 21:37:45 +08:00
Donald Zou c2642259b4 Update gunicorn.conf.py 2024-12-12 18:31:00 +08:00
Donald Zou c47b0c9741 Update gunicorn.conf.py 2024-12-12 18:29:11 +08:00
Donald Zou f425156cad Sync with HTTPS build 2024-12-12 18:23:42 +08:00
Donald Zou 7d7e31120e HTTPS workflow 2024-12-12 18:20:25 +08:00
dselen ccd247d154
Merge branch 'dev' into v4.2-dev 2024-12-12 11:07:17 +01:00
Daan 53df6849f7 Working prototype with AWG 2024-12-12 11:03:39 +01:00
Donald Zou c2080bd1b3 Update gunicorn.conf.py 2024-12-12 16:34:45 +08:00
Donald Zou 9e93f8c2a5 HTTPS workflow 2024-12-12 16:25:54 +08:00
Donald Zou 907a142c8d Build 2024-12-09 16:13:22 +08:00
Donald Zou d7bc8cd8e4 Update configurationList.vue 2024-12-09 16:13:06 +08:00
Daan bb3e00a695 Thanks to @NOXCIS, added this development Ubuntu Dockerfile. 2024-12-08 14:22:02 +01:00
alexp af5e7974c3 Added the french language 2024-12-07 21:29:59 -05:00
Donald Zou 6a88959ec4 Update dashboard.py 2024-12-07 21:59:03 +08:00
Donald Zou 7b59149f90 Build 2024-12-07 21:41:20 +08:00
Donald Zou 3e01079caf Added sorting and search to configuration list 2024-12-07 21:41:05 +08:00
Donald Zou d92e62e40b Adjusted system status widget 2024-12-07 21:40:43 +08:00
Donald Zou b4952dea7b Fixed message center
- Fixed message blocking element behind
- Adjusted message UI
- Allow to dismiss, will not dismiss when mouse hover on it
2024-12-07 17:48:48 +08:00
Donald Zou 00acb04329 Update dashboard.py
- Disabled `EmailSender` for now
2024-12-07 17:05:30 +08:00
Donald Zou 939dd0591e Build 2024-12-07 16:54:00 +08:00
Donald Zou febdb2a9e0 Update deleteConfiguration.vue 2024-12-06 22:55:02 +08:00
Donald Zou bcfd9fc1c9 Update wgd.sh 2024-12-06 22:11:34 +08:00
Donald Zou e4964da5d4 Update wgd.sh 2024-12-06 22:05:18 +08:00
Donald Zou a042298a4a Update wgd.sh 2024-12-06 22:03:57 +08:00
Donald Zou 908be168a9 Update wgd.sh 2024-12-06 22:01:57 +08:00
Donald Zou d780bb3937 Added PyPI choose in `wgd.sh install` 2024-12-06 22:00:35 +08:00
Donald Zou 461e7e8913 build 2024-12-06 20:32:59 +08:00
Donald Zou 819e8b73c3 Added configuration editor 2024-12-06 20:27:04 +08:00
Donald Zou 627b7087a1 Build 2024-12-06 01:27:15 +08:00
Donald Zou f23cf555e1 Added file upload to create configuration 2024-12-05 22:22:55 +08:00
Donald Zou 000978e4fb
Merge pull request #538 from donaldzou/v4.2-dev-amneziawg-support
Add AmneziaWG Support
2024-12-05 01:52:35 +08:00
Donald Zou 74782483bd Build 2024-12-05 01:51:02 +08:00
Donald Zou c5f9387b92 All AWG support work is done ;) 2024-12-05 01:50:31 +08:00
Donald Zou 57583b6747 Backup and restore for AWG is done 2024-12-04 17:50:16 +08:00
Donald Zou 434c236210 Finished adjusting Peers UI for AWG 2024-12-03 02:34:45 +08:00
Donald Zou 807bb97b6a Update some UI to handle Wireguard and AWG 2024-12-02 17:36:37 +08:00
Donald Zou da53bd44d1 Update dashboard.py 2024-12-02 16:38:09 +08:00
Donald Zou 3b01943649 Update dashboard.py 2024-12-02 16:34:26 +08:00
Donald Zou 3340f9c6ee Update dashboard.py
- Updated WireguardConfiguration class to handle awg configuration files
- Added AmneziaWireguardConfiguration class as a subclass of WireguardConfiguration
2024-12-02 15:09:54 +08:00
Donald Zou b21cfe8504 Updated search bar 2024-12-02 14:13:19 +08:00
Donald Zou 97ab6ec299 Build 2024-12-01 01:07:47 +08:00
Donald Zou 7fda58e5c8 New search bar design 2024-12-01 01:07:27 +08:00
Donald Zou db6e820d1d Build 2024-11-30 00:28:12 +08:00
Donald Zou 8c2e1875ca Update peerSearch.vue 2024-11-30 00:27:59 +08:00
Donald Zou bd95fe9af1 Build 2024-11-29 23:08:14 +08:00
Donald Zou a517f89234 Update 2024-11-29 23:07:58 +08:00
Donald Zou f994e7bfa8 Update dashboard.py
- Accepted proposed enhancement on #533
2024-11-29 14:42:13 +08:00
Donald Zou 28716924c9 Update dashboard.py 2024-11-28 23:36:42 +08:00
Donald Zou b597f90f5b Update 2024-11-28 23:35:22 +08:00
Donald Zou 5912420467 Added System Status 2024-11-28 23:28:10 +08:00
Donald Zou 90f35fd680 update 2024-11-27 22:53:27 +08:00
Donald Zou 3e2d6e71b9 Update 2024-11-27 21:53:00 +08:00
Donald Zou cbffdd829a Update 2024-11-27 18:26:13 +08:00
Donald Zou d77e092948 Build 2024-11-27 14:27:41 +08:00
Donald Zou 1f5e10e784 Adjusted login UI 2024-11-27 14:25:38 +08:00
Donald Zou 68e3813c6c Update 2024-11-27 13:57:21 +08:00
Donald Zou c9d78e3f67 Added tooltips to dropdown icons
#524
2024-11-26 22:43:33 +08:00
Donald Zou b4f3fb3b30 Fixed UI 2024-11-26 21:32:08 +08:00
Donald Zou bf7fb898f9 Update dashboard.py
- Fixed #497
2024-11-26 21:27:32 +08:00
Donald Zou 6c5e0543b4 update 2024-11-26 00:21:59 +08:00
Donald Zou 578a1db62f Refactored some of the codes 2024-11-25 22:11:51 +08:00
Donald Zou 4524a55b23
Merge pull request #527 from donaldzou/fix-#516
Security Patch
2024-11-25 20:41:51 +08:00
Donald Zou f942eaf1b6 Ready for release 2024-11-25 18:23:48 +08:00
Donald Zou 6a4d16fae9
Merge pull request #529 from donaldzou/fix-#522
Fixing `auth_req` is not working
2024-11-25 02:49:40 +08:00
Donald Zou 53b234252f Final build 2024-11-25 02:48:55 +08:00
Donald Zou 9287e81ef1 Build 2024-11-25 01:53:29 +08:00
Donald Zou 5462326f79 Updated `dashboard.py` and `DashboardConfigurationStore.js`
- Added `session.clear()` to clean the session specific to each login session
- Clear all cookie with the `signout` request and also clear again (just in case) in the frontend code.
2024-11-25 01:46:27 +08:00
Donald Zou b8b3992159 commit 2024-11-24 12:10:00 +08:00
Donald Zou 8214000713 Commit 2024-11-24 00:22:33 +08:00
Donald Zou 94337a33d4
Merge pull request #521 from donaldzou/bsd-support
Replace `ifcfg` with socket to get default interface address
2024-11-23 18:25:02 +08:00
Donald Zou 8ddee03338
Merge pull request #520 from donaldzou/main
Merging latest v4.1.1 changes into v4.2.0 branch
2024-11-23 18:10:00 +08:00
Donald Zou 8e2934533b Dropping support plan to OpenBSD 2024-11-23 18:08:14 +08:00
Donald Zou 71ee784003 Update dashboard.py 2024-11-23 17:51:48 +08:00
Donald Zou 597528e9b7 Update dashboard.py 2024-11-23 17:30:34 +08:00
Donald Zou 45fbbf9218 Update dashboard.py 2024-11-23 17:30:22 +08:00
Donald Zou c7a4a01fee
Merge pull request #519 from donaldzou/main
Merge main into bsd support
2024-11-23 16:57:00 +08:00
Donald Zou fa04ad1395
Merge pull request #518 from donaldzou/v4.1.1-dev
Ready for new update
2024-11-23 15:57:28 +08:00
Donald Zou abdd85bdd5 Ready for new update 2024-11-23 15:37:00 +08:00
Donald Zou dba55d57e8
Merge pull request #488 from donaldzou/language-template
Added template and script to check local files
2024-11-23 15:18:14 +08:00
Donald Zou 9819c96717 Update verify_locale_files.py 2024-11-23 15:09:35 +08:00
Donald Zou 1b0885bffe Update verify_locale_files.py 2024-11-23 14:55:07 +08:00
Donald Zou c0a8540ab8 Update tr-tr.json 2024-11-23 14:38:15 +08:00
Donald Zou f3b95f03a1 Update active_languages.json 2024-11-23 13:46:27 +08:00
Donald Zou 5f06f3da52 Update sv-se.json 2024-11-23 13:46:20 +08:00
Donald Zou 10a7fb809b Update pl.json 2024-11-23 13:37:27 +08:00
Donald Zou d3ea1f3da2 Update nl-nl.json
Fixed translation
2024-11-23 13:36:58 +08:00
Donald Zou 74d0471a6f Update verify_locale_files.py 2024-11-23 13:23:50 +08:00
Donald Zou a1c1805409 Updated it-it.json 2024-11-23 13:23:47 +08:00
Donald Zou b843145bed Update de-de.json
Fixed de-de translations
2024-11-23 13:15:03 +08:00
Donald Zou 119920b665 Update ru.json
Fixed translations
2024-11-22 19:36:11 +08:00
Donald Zou 6190e2d290 Fixed cs.json 2024-11-22 01:46:52 +08:00
Donald Zou 79de99d146 Finalized verify script 2024-11-22 01:41:11 +08:00
Donald Zou 56a9b3df0a
Merge pull request #514 from MickLesk/patch-1
Update de-de.json
2024-11-22 01:32:56 +08:00
Donald Zou 4df6413dba
Merge pull request #513 from oskarax/main
Added Swedish translations and updated active languages file.
2024-11-22 01:30:28 +08:00
Donald Zou fcd0ada639
Merge branch 'language-template' into main 2024-11-22 01:30:17 +08:00
Donald Zou 6b6bd155aa
Merge pull request #512 from donaldzou/main
Merging main into language template branch
2024-11-22 01:28:23 +08:00
CanbiZ 9c91b1bca5
Update de-de.json 2024-11-18 23:05:05 +01:00
Oskar Ax 1a5c0e5b24 Added Swedish translations and updated active languages file. 2024-11-18 21:08:06 +01:00
Donald Zou 981b30e4af Update check script 2024-11-17 17:20:41 +08:00
Donald Zou 30c84d8cbf
Merge pull request #502 from AdimoUK/main
Merge spanish translation
2024-11-16 19:17:46 +08:00
Donald Zou 4c538c54a8
Merge pull request #501 from AdimoUK/polish-language
Merge Polish translation
2024-11-16 19:12:01 +08:00
dselen ad7a8f1d08
Merge pull request #496 from DaanSelen/v4.1-add-qemu
Previous PR was merged too fast 🥇
2024-11-15 12:11:18 +01:00
Daan f02a07dab4 Name change 2024-11-15 12:08:51 +01:00
dselen c61b0fc1e1
Merge pull request #494 from donaldzou/fix-preshared-key
Fixed #377
2024-11-15 12:06:14 +01:00
Daan fed7dfcb73 Added arm/v6
Specified the QEMU setup. From WireGate
2024-11-15 12:05:08 +01:00
Donald Zou 0d69c811ab
Merge pull request #495 from DaanSelen/v4.1-add-qemu
Added QEMU virtualization for cross-compilation
2024-11-15 18:59:48 +08:00
Daan 83274add22 Added QEMU virtualization for cross-compilation @NOXCIS 2024-11-15 11:35:45 +01:00
Donald Zou 7497d1b6d4 Fixed #377
Fixed Preshared Key is not added when adding peers
2024-11-15 13:45:14 +08:00
Donald Zou 239bc144e3 Fixed both Chinese translation 2024-11-15 12:00:31 +08:00
Donald Zou 1ece64abe1
Merge pull request #492 from DaanSelen/language-template
Proposed changes
2024-11-15 11:52:13 +08:00
Daan 915783699b Proposed changes 2024-11-14 21:36:27 +01:00
Donald Zou 43f6bd41c9 Update verify_locale_files.py 2024-11-15 02:15:40 +08:00
Adam 98c017dd56 Fix to a translation 2024-11-14 00:09:59 +00:00
Donald Zou 811ed3251b
Merge pull request #464 from jpizquierdo/v4.1-dev 2024-11-14 02:06:25 +08:00
Donald Zou 665b0f1484 Added template and script to check local files 2024-11-14 02:02:26 +08:00
Donald Zou 9e26d845da
Update active_languages.json
Oops.. missed a comma
2024-11-14 00:59:17 +08:00
Donald Zou 6ddf20f5ce
Update active_languages.json
Sorting the list based on `lang_id`
2024-11-14 00:53:26 +08:00
dselen 27ed0710cc
Merge pull request #487 from donaldzou/donaldzou-patch-1
Update README.md Discord link
2024-11-13 15:08:24 +01:00
Donald Zou cb87c9f345
Update README.md Discord link 2024-11-13 21:12:45 +08:00
Donald Zou e1e147c8f0 Adding support to OpenBSD 2024-11-12 18:32:53 +08:00
Donald Zou b37d889de9 Update wgd.sh 2024-11-12 18:21:27 +08:00
Donald Zou b61c9bfc5e Update wgd.sh 2024-11-12 18:01:59 +08:00
Donald Zou e963788a81 Update wgd.sh 2024-11-12 17:58:51 +08:00
Donald Zou 3ef4798e09 Update wgd.sh 2024-11-12 00:12:10 +08:00
Donald Zou 21730102ae
Merge pull request #474 from donaldzou/DaanSelen-lang-1 2024-11-11 19:12:12 +08:00
Donald Zou b1ae05152e
Merge pull request #471 from kaanklky/languages-tr-tr 2024-11-11 19:05:53 +08:00
Adam cc87f01e2f Addition of Polish Language 2024-11-10 22:07:20 +00:00
dselen 85dfa1b078
Update nl-nl.json 2024-11-10 13:53:47 +01:00
Donald Zou fdff54e11e
Merge pull request #469 from DaanSelen/main 2024-11-10 09:02:06 +08:00
Kaan Kölköy 0894f38660 Update active languages list 2024-11-09 22:40:04 +01:00
Kaan Kölköy f643d8ef25 Add Turkish localization 2024-11-09 22:39:51 +01:00
dselen e437284980
Squash Docker progress: (#1)
* Version 4.1 Docker image tested.

    Fixed kinks in Docker image.
    Updated Dutch language
    Removed remaining "enable" parameter from Docker-related files
    Made the symlink system more reliable
    Improved updatability.

    Added multiplatform docker build (arm,arm64 and amd64)
    More verbose logging from the Docker image.
2024-11-09 00:18:01 +01:00
Donald Zou f6e0d330ac update 2024-11-09 02:17:21 +08:00
Donald Zou 890cbf0f36
Merge pull request #468 from donaldzou/issue-discord-link
Fix Discord Link Under Help
2024-11-09 00:49:46 +08:00
Donald Zou 36cf2ea2e8
Merge branch 'main' into issue-discord-link 2024-11-09 00:49:26 +08:00
Donald Zou a8f7bd0280 Build 2024-11-09 00:48:16 +08:00
Donald Zou aa6517046d
Merge pull request #467 from donaldzou/issue-#462
Fix #462
2024-11-09 00:44:45 +08:00
Donald Zou 42e4d8c7ba build 2024-11-09 00:40:42 +08:00
Joel Pérez Izquierdo 3d1da38cbc add es-es locale file 2024-11-08 17:29:58 +01:00
Joel Pérez Izquierdo 5ae8490999 add spanish language 2024-11-08 17:28:57 +01:00
Donald Zou 715675b2f1
Merge pull request #458 from donaldzou/donaldzou-patch-1
Update README.md
2024-11-08 16:17:28 +08:00
Donald Zou 0d7198a26c
Update README.md 2024-11-08 16:17:15 +08:00
Donald Zou d8b7ce6d9b Update README.md 2024-11-08 16:16:44 +08:00
Donald Zou d032ca31e3 Update README.md 2024-11-08 16:15:56 +08:00
Donald Zou 67b49c0585
Merge pull request #457 from donaldzou/v4.1-dev
Final update
2024-11-08 16:14:14 +08:00
Donald Zou c23242f18e Final update 2024-11-08 16:13:47 +08:00
Donald Zou c23f7a3ee2
Merge pull request #440 from donaldzou/v4.1-dev
V4.1 dev
2024-11-08 16:13:22 +08:00
Donald Zou 7e3e957649 Update ru.json 2024-11-08 00:50:57 +08:00
Donald Zou b8f304dfd3 Prepare for release 2024-11-08 00:31:19 +08:00
Donald Zou e895ac9245
Merge branch 'main' into v4.1-dev 2024-11-08 00:29:01 +08:00
Donald Zou 77579a71e8 Match docker 2024-11-07 23:54:48 +08:00
Donald Zou cc7e13cdbf
Merge pull request #455 from DaanSelen/v4.1-dev
Added Dutch language.
2024-11-07 23:53:48 +08:00
Donald Zou 5df3886e42
Merge branch 'v4.1-dev' into v4.1-dev 2024-11-07 23:50:54 +08:00
Donald Zou cc179a05d0 Update 2024-11-07 23:47:08 +08:00
Donald Zou ec5f4f2778 Fixed some CSS issue on mobile 2024-11-07 23:43:14 +08:00
Donald Zou 084c95f0e5 Just fixed #455 2024-11-07 18:33:39 +08:00
Daan 3b78cfe0fd Renamed files and changed Dutch file. 2024-11-07 11:26:37 +01:00
Daan 160ce3338d Changed some file-names 2024-11-07 11:25:05 +01:00
Donald Zou d425cfe1aa Refactor 2024-11-07 14:56:02 +08:00
Donald Zou 6ff286f9b4 Refactor 2024-11-07 14:54:42 +08:00
Donald Zou b930461434 Refactored some files 2024-11-07 14:53:31 +08:00
Donald Zou ee6bb24506 build test 2024-11-07 10:37:11 +08:00
Donald Zou b69124c89f test 2024-11-07 00:15:28 +08:00
Donald Zou f089e4a1ce update 2024-11-07 00:09:40 +08:00
Donald Zou 7dedb5c076 Test adding hash to vite build 2024-11-07 00:03:40 +08:00
Donald Zou 3276c9a84c Update 2024-11-06 23:25:20 +08:00
Donald Zou 04ef2ea3b6 Merge branch 'v4.1-dev' of https://github.com/donaldzou/WGDashboard into v4.1-dev 2024-11-06 23:22:01 +08:00
Donald Zou 04a2f27a75 Update 2024-11-06 23:21:53 +08:00
Donald Zou d962f59884
Merge pull request #452 from jursed/v4.1-dev
Czech translation
2024-11-06 21:40:18 +08:00
Donald Zou fd3bd4567a
Update active_languages.json
Removed the `,` at the end :)
2024-11-06 21:12:55 +08:00
Donald Zou 29e7a5ecdb Fixed bulk add 2024-11-06 20:58:53 +08:00
Donald Zou 4956b0d89d Rename configuration done 2024-11-06 18:36:55 +08:00
Donald Zou 3bc54a4e16 Update wgd.sh 2024-11-05 19:46:09 +08:00
Donald Zou 29ecd602ec Added help modal 2024-11-05 19:43:47 +08:00
zire 1425dec564 Czech translation
First attempt at Czech translation. Note that some words don't have
a good and short translation, e.g. "peers", "pre-shared key" or
"persistent keepalives". In these cases, some approximate/familiar
"Czech-English" amalgamation is used.
2024-11-03 22:05:13 +01:00
Donald Zou 2bb1c0c999 Optimized build 2024-11-03 21:06:51 +08:00
Donald Zou 97f3daae70 Optimized vite build 2024-11-03 20:45:59 +08:00
Donald Zou c53a7ef6fe Update 2024-11-03 19:09:15 +08:00
Donald Zou b97fc16ad9 Adjusted router to dynamic import 2024-11-03 18:35:21 +08:00
Donald Zou eaac12ddc8 Update both Chinese languague 2024-11-03 17:36:03 +08:00
Donald Zou 183be5da0e
Update README.md
testing webhooks
2024-11-03 15:58:34 +08:00
Donald Zou ace0953c87
Update README.md 2024-11-03 15:26:33 +08:00
Donald Zou 1fd7e7833d
Update README.md 2024-11-03 15:20:41 +08:00
Donald Zou aa5801d73b
Update README.md 2024-11-03 14:41:21 +08:00
Donald Zou d1ea8081e4 Finished zh-CN 2024-11-02 18:50:15 +08:00
Donald Zou 49467c906d zh-CN Update 2024-11-02 14:26:47 +06:00
Donald Zou 79bd575aca
Merge pull request #448 from donaldzou/DaanSelen-patch-1
Hotfix.
2024-11-02 16:20:35 +08:00
Donald Zou e533a0e405
Merge pull request #445 from knd775/patch-1
feat: Support Rocky Linux
2024-11-01 23:24:47 +08:00
Donald Zou 94b597d29a Adjusted some UI 2024-11-01 22:30:25 +08:00
dselen 504fefff94
Update docker-build.yaml 2024-11-01 09:19:47 +01:00
dselen ff794a3638
Update docker-analyze.yaml
I switched them around 😭
2024-11-01 09:15:46 +01:00
Donald Zou efb9a34cb8
Merge pull request #447 from DaanSelen/workflow-change 2024-11-01 16:11:26 +08:00
Daan a93291b38f Changed the workflow from every night to: on every commit in the main branch. 2024-10-31 20:16:45 +01:00
Donald Zou dfc9eab9d3 Fixed some issue on autostart 2024-10-31 23:28:30 +08:00
Donald Zou 3c74b0b1ef Update 2024-10-31 22:13:44 +08:00
Donald Zou 9b981fcea8 Autostart feature is done 2024-10-31 22:13:00 +08:00
Ben Ayles 9d3a189d77
fix: Support Rocky Linux 2024-10-29 23:02:59 -04:00
Donald Zou 01c0175e8f Commit 2024-10-29 14:57:29 +08:00
Donald Zou d7e5e2f381 Update wgd.sh 2024-10-26 18:09:42 +08:00
Donald Zou c27de8b9a5 Build 2024-10-26 13:54:08 +08:00
Donald Zou c3a55f8b69 Finished create configuration from backup 2024-10-25 23:34:07 +08:00
Donald Zou 78b65a799e Added notification to backup before delete 2024-10-25 23:33:42 +08:00
Donald Zou 6a30cee611 Adjusted some animation 2024-10-25 23:33:26 +08:00
Donald Zou bc19e3a6ff Adjusted title 2024-10-25 23:33:07 +08:00
Donald Zou 5a8f2fa2be Adjusted some animation 2024-10-25 23:32:30 +08:00
Daan 81168c27c6 Fixed issue https://github.com/donaldzou/WGDashboard/issues/329.
Regarding being able to pass in the -y flag.
2024-10-24 23:10:36 +02:00
Donald Zou a606626053 UI for restore configuration is done 2024-10-25 00:19:27 +08:00
Daan adeb57864b Fixed incorrect Docker_IMAGE variable from dselen/ -> donaldzou/ 2024-10-24 10:48:14 +02:00
Daan 747f1a6fae SEC: Fixed CVE-2024-9143 presence. 2024-10-24 10:24:22 +02:00
Daan 3ac9c23573 Removed the default value: wg0 in isolate and enable.
Removed clean_up() function because persistency is done differently.
Overal tried to make readability better in entrypoint.sh
Fixed bug where local config variable causes issues.
Applied ShellCheck recommendations.
2024-10-24 10:13:33 +02:00
Daan 5ad9c0e77a Fixed issue where the wg0.conf template got obliterated.
Moved it to a safe spot.
2024-10-24 00:23:12 +02:00
Daan ba5ba2f1d6 Removed copy step in entrypoint.sh
Tested updating, works as long as presistent files are compatible.
2024-10-24 00:09:27 +02:00
Daan 4902b5f351 Initial testing to update from version 4.0.3 to 4.0.4 have succeeded! 2024-10-23 23:47:00 +02:00
Daan 166fcda193 Minor changes to compose and Dockerfile. 2024-10-23 23:15:41 +02:00
Daan 83560bc775 Changed around Docker image building and entrypoint.
- Succeeding my tests.
2024-10-23 22:40:40 +02:00
Daan 4ffb00c9f5 Updated ensure install. 2024-10-23 16:57:51 +02:00
Daan fbac41a774 Changed ensure install 2024-10-23 16:57:40 +02:00
Daan c837ab8693 Complete Docker Container redo, making updates possible.
- Through symlinks.

Refactored the set env variables function.
2024-10-23 16:41:03 +02:00
Donald Zou 1cc321ddff
Merge pull request #436 from donaldzou/v4.1-workflows
V4.1 workflows
2024-10-23 17:30:33 +08:00
dselen 7861cffb91
Merge pull request #430 from DaanSelen/main
Modified GitHub workflows
2024-10-22 08:52:52 +02:00
Donald Zou 82a472f368 Delete configuration, restore configuration 2024-10-22 12:28:51 +08:00
Daan cfe59774e7 Changed Docker image names: dselen -> donaldzou 2024-10-21 12:09:53 +02:00
Daan 1098475473 Changed failing criteria 2024-10-21 12:07:33 +02:00
Donald Zou 4546e795ef
Merge pull request #433 from reloadlife/patch-1
Added a try/catch statement to prevent panel from crashing #432
2024-10-21 15:37:49 +08:00
Donald Zou bb0aba586b
Update dashboard.py
Instead of catching one sql statement error, I moved the catch statement to `sqlSelect` to prevent all database error
2024-10-20 16:05:32 +08:00
Mohammad Mahdi "Mamad" Afshar 204b995e6c
added a try/catch statement to prevent panel from crashing #432 2024-10-19 19:25:38 +03:30
Daan 321b7b4cee Modified GitHub workflows 2024-10-17 12:52:34 +02:00
Donald Zou 413377fbb9 Backup and restore function is done
Completed with SQLDump and .conf backup
2024-10-17 00:04:26 +08:00
Donald Zou e2e821881c Peer restore works
Still need to SQL Dump the table aswell
2024-10-16 17:44:49 +08:00
Donald Zou 51712ed2a8 Re-adjust backup & restore location 2024-10-15 23:07:49 +08:00
Donald Zou 27de7ddbf8 Still working on backup & restore 2024-10-15 00:30:20 +08:00
Donald Zou bb700f3a3d Update selectPeers.vue 2024-10-14 17:27:34 +08:00
Donald Zou 563268558b Finished selecting peers in bulk to delete and download 2024-10-14 16:32:17 +08:00
Donald Zou 56744cec7b Commit 2024-10-06 23:09:55 +08:00
Donald Zou 87696a2e6c Fixed 2024-10-06 17:27:16 +08:00
Donald Zou 79f1c20f8a Added custom build.sh 2024-10-06 17:24:53 +08:00
Donald Zou 4e5638df36 Fixed issue #346 2024-10-06 17:13:50 +08:00
Donald Zou 71349f35e4 Fixed issue #352
Fixed issue when allowed_ips have more than 1 IP Address, allow access will crash
2024-10-06 16:59:11 +08:00
Donald Zou 2cb06bb4bb Added total peers in peers count 2024-10-05 16:10:36 +08:00
Donald Zou 5e1769b81f Updated Translation 2024-10-05 15:38:56 +08:00
Donald Zou e344f28265 Added peer's endpoint IP back 2024-10-05 15:03:27 +08:00
Donald Zou 2297d6b85b
Merge pull request #417 from donaldzou/v4.0.4-almalinux-support
Added AlmaLinux Support
2024-10-05 10:24:46 +08:00
Donald Zou baaecdbd8c
Update wgd.sh 2024-10-05 10:05:48 +08:00
Donald Zou 31d8af642d
Merge pull request #363 from martin-g-it/patch-1
Update dashboard.py - sort WG configurations
2024-10-04 20:15:04 +08:00
Donald Zou 27c67ec202 Added loading bar between each routes 2024-10-04 17:48:24 +08:00
Donald Zou 4833a29e57 Adjusted some transition between routes 2024-10-04 17:27:25 +08:00
Donald Zou 4a2f3e0372 Configuration Settings done 2024-10-04 16:58:47 +08:00
Donald Zou f3eeff4ec8
Merge pull request #415 from donaldzou/DaanSelen-patch-1
Fix README.md
2024-10-04 16:22:16 +08:00
dselen 221e03ecfa
Fix README.md 2024-10-03 16:03:42 +02:00
dselen 392a9c6f53
Merge pull request #414 from DaanSelen/main
Second try. Merged Docker update.
2024-10-03 15:59:33 +02:00
Dselen 145d12b2c8 Added minor changes and deletions. 2024-10-03 08:26:30 -05:00
Dselen 0c5033ff79 fixed issues with commits 2024-10-03 08:23:17 -05:00
Donald Zou 5f46f54dfd Updated mobile UI
Using dvh and vh as fallback for mobile browsers.
2024-10-03 21:20:15 +08:00
Dselen d4819b13eb working tests 2024-10-03 08:15:21 -05:00
Donald Zou 9dd9be3b8c
Merge branch 'main' into main 2024-10-03 20:59:58 +08:00
Dselen dd38809866 Rebase 2024-10-03 07:45:24 -05:00
Donald Zou c414725f12
Merge pull request #406 from 3vis97/v4.1-dev-italian
Added italian language issues/#397
2024-10-03 20:13:46 +08:00
Donald Zou d3c8a350a4
Merge branch 'v4.1-dev' into v4.1-dev-italian 2024-10-03 20:13:38 +08:00
Donald Zou e04b748b91
Merge pull request #345 from DaanSelen/conflict-resolved
Updated Docker image
2024-10-03 20:09:31 +08:00
Donald Zou 1eee8c4725 Update 2024-10-03 20:07:50 +08:00
dselen 2b7a4b170c
Merge branch 'main' into conflict-resolved 2024-10-03 12:17:18 +02:00
Donald Zou 7c0bf4f137 Fixed #357: Brought back IP Address and Port
But still manually restart WGDashboard is needed
2024-10-03 15:24:50 +08:00
Donald Zou 54bae43d2e Update osmap.vue
Added "Dark Mode" to OSM
2024-10-02 22:00:53 +08:00
Donald Zou 25be1155a6 Added testing to OSM before loading map
Prevent users in locations blocked OSM seeing an empty div
2024-10-02 21:53:57 +08:00
Donald Zou 4905d61a1a Some adjustment on map 2024-10-02 18:01:20 +08:00
Donald Zou ff0147bebb Added OpenStreetMap for Ping and Traceroute 2024-10-02 17:09:35 +08:00
Donald Zou 7fe4889b6e Updated some languages 2024-10-02 17:09:04 +08:00
Donald Zou 3c9b1b1833 Thanks to Pixnet for providing the Russian translation :) 2024-09-27 22:07:50 +08:00
Dselen 2a46c873b8 Removed Debian container 2024-09-27 03:22:03 -05:00
Trevis97 6ed09324ad Updated Italian translations and fixed typos upstream/issues/#397 2024-09-26 22:32:43 +02:00
Mattia Trevisani d1f9c10790 Added italian language upstream/issues/#397 2024-09-26 22:31:27 +02:00
Donald Zou 5d041b2fd3
Update README.md 2024-09-25 18:01:05 +08:00
Donald Zou 5cea0fa87b
Merge pull request #401 from orangeferdi/patch-3
Create de.json
2024-09-25 00:11:05 +08:00
Donald Zou 6f140eef29
Merge pull request #400 from orangeferdi/patch-2
Update active_languages.json
2024-09-25 00:10:52 +08:00
Donald Zou e5850adfd7 Update dashboard.py 2024-09-24 22:54:18 +08:00
orangeferdi 6921833ce2
Create de.json
Adding german language
2024-09-24 10:13:34 +02:00
orangeferdi 57b9accd97
Update active_languages.json
Adding german language
2024-09-24 10:10:16 +02:00
Donald Zou de94f5b233
Update README.md 2024-09-24 00:40:20 +08:00
Donald Zou b5519a73fb
Merge pull request #398 from shuricksumy/v4.1-dev-uk
Add Ukrainian language
2024-09-23 16:32:00 +08:00
Oleksandr Suchkov c5d8c2c355 Add Ukrainian language 2024-09-22 23:58:38 +01:00
Donald Zou c5b02a426c
Update README.md 2024-09-23 03:12:06 +08:00
Donald Zou 4cae910b68 Added some update :) 2024-09-23 03:07:48 +08:00
Donald Zou b664fccce2 Finished Chinese Simplified and Traditional translation 2024-09-23 00:17:30 +08:00
Donald Zou 21672f99d1 Translating to zh-cn is done 2024-09-22 21:50:30 +08:00
Donald Zou c269e46892 Added some new translation 2024-09-22 16:44:36 +08:00
Donald Zou 8534e8cf5b Still working on translating.... 2024-09-22 16:33:22 +08:00
Donald Zou 94bf1c2484 Finished translating most of the UI. Left with notifications 2024-09-19 22:32:15 +08:00
Donald Zou b3b30470fc Merge branch 'main' of https://github.com/donaldzou/WGDashboard 2024-09-19 14:45:11 +08:00
Donald Zou 41d91e75fc Prepare for v4.0.4 2024-09-19 14:44:49 +08:00
Donald Zou a97a91b844
Merge pull request #395 from donaldzou/fix-#391
Fixed issue where Preshared Key is not added when unrestricted peers
2024-09-19 14:31:41 +08:00
Donald Zou f1c577ab76 Fixed issue mentioned in #391 2024-09-19 14:21:38 +08:00
Donald Zou 17a9fe5024 Fixed issue mentioned in #391 2024-09-17 14:42:25 +08:00
Donald Zou d74a76dee3
Merge pull request #389 from donaldzou/v4.0.3-fix
Merge new changes to v4.1
2024-09-14 16:23:17 +08:00
Donald Zou c01201b88e
Merge branch 'v4.1-dev' into v4.0.3-fix 2024-09-14 16:23:08 +08:00
Donald Zou 833bdf878a Update dashboard.py 2024-09-14 15:49:41 +08:00
Donald Zou 891990b2f1
Merge pull request #387 from donaldzou/v4.0.3-fix
Ready for v4.0.3
2024-09-14 15:44:26 +08:00
Donald Zou e9ab7029c9 Ready for v4.0.3 2024-09-14 15:21:10 +08:00
Donald Zou 6f681dba09
Merge pull request #386 from donaldzou/v4.0.3-fix
Update privatePublicKeyInput.vue
2024-09-14 15:14:50 +08:00
Donald Zou b3edff947d Update privatePublicKeyInput.vue
Fixed issue mentioned in #375
2024-09-14 15:13:47 +08:00
Donald Zou ce6d80d601 Update dashboard.py 2024-09-14 15:11:06 +08:00
Donald Zou a80a2866de Almost done... 2024-09-13 16:32:56 +08:00
Donald Zou 02c2221970 Still working on localization 2024-09-12 15:21:42 +08:00
Donald Zou d35bd6e75b
Merge pull request #379 from donaldzou/DaanSelen-patch-1
Update README.md
2024-09-12 12:17:24 +08:00
dselen f3a2f98864
Update README.md
Removed Docker notice, because it is no longer relevant.
2024-09-10 10:12:57 +02:00
Donald Zou a3a312e3db Still working on translation... 2024-09-09 23:43:55 +08:00
dselen f5cb5c4516
Merge branch 'main' into conflict-resolved 2024-09-09 09:01:07 +02:00
Donald Zou d458a28337 Started working on localization 😏 2024-09-06 22:38:56 +08:00
Donald Zou a102a780f8 Adjusted search functions 2024-09-06 17:15:38 +08:00
Donald Zou 4dbbc32108 Updated Bootstrap Icons 2024-09-06 16:32:05 +08:00
Donald Zou b6aedb43ee Fixed updating WG configuration path 2024-09-06 16:31:54 +08:00
Donald Zou ec50dcbbd9 Preshared Key switch is added for both single and bullk add peers 2024-09-05 17:10:24 +08:00
Donald Zou bcc983f11f Update presharedKeyInput.vue
Added preshared key switch
2024-09-05 16:43:46 +08:00
Donald Zou 809651054e Update dashboard.py 2024-09-05 16:17:56 +08:00
Donald Zou 2e965ceb9e Update dashboard.py 2024-09-05 14:51:00 +08:00
Martin e35f942964
Update dashboard.py - sort WG configurations
Sort WG configurations alphabetically
2024-09-04 14:40:04 +02:00
Dselen 40e6fce281 Fix typos 2024-08-27 02:30:01 -05:00
dselen 2139865876
Merge pull request #1 from DaanSelen/dev
Dev
2024-08-27 09:28:16 +02:00
Dselen c3cda05d98 Move fix. 2024-08-27 02:26:19 -05:00
Dselen 548f3db33d quickfix 2024-08-26 16:16:43 -05:00
Dselen a76e9ed98b Testing more changes for better handling of variables. 2024-08-26 16:16:09 -05:00
Dselen c0ef41a9bb Forgot to reorder 2024-08-26 15:54:27 -05:00
Dselen f6e5d9675a Alternative testing. 2024-08-26 15:53:53 -05:00
Dselen ef028659d8 Testing improvement 2024-08-26 15:46:17 -05:00
Dselen 40f39e918d Finished work for now on the alpine docker image. 2024-08-26 15:28:27 -05:00
Dselen 2ec3ee2734 Fixed typo 2024-08-26 13:42:08 -05:00
Dselen bc29b89a16 Rebased and going further 2024-08-26 13:07:42 -05:00
Donald Zou e21853286e Did some refactor on wgd.sh 2024-08-25 16:26:36 +08:00
Donald Zou c012b8c4a5
Merge pull request #340 from donaldzou/v4.0-alpine-linux
V4.0 alpine linux
2024-08-25 16:19:23 +08:00
Donald Zou 48f6c28556
Merge pull request #334 from NOXCIS/main
Fixed Docker Image
2024-08-25 16:18:34 +08:00
Noxcis 0c1502f801 Streamiline
+ Added Docker Install arg to wgd.sh
2024-08-25 03:01:21 -05:00
Donald Zou fec20ed381 Reduced the time to open the config file 2024-08-25 15:59:48 +08:00
Donald Zou 252c147dcf Update dashboard.py 2024-08-25 15:15:07 +08:00
Donald Zou 453d474104 Fixed multiple IP address on a Configuration 2024-08-25 15:14:09 +08:00
Donald Zou 84cf4a9b66 Update README.md 2024-08-25 11:43:06 +08:00
Noxcis fb016bebde Update wgd.sh 2024-08-24 20:04:07 -05:00
Noxcis 8f6a738481 Docker 2 Stage 2024-08-24 20:02:34 -05:00
Noxcis b07f958577 Update compose.yaml 2024-08-24 06:46:06 -05:00
Noxcis 8da0fde52a + 2024-08-24 06:45:13 -05:00
Donald Zou 39be16cb63
Update README.md 2024-08-24 19:37:18 +08:00
Donald Zou 59d0c0def4
Merge pull request #337 from donaldzou/v4.0-alpine-linux
V4.0 alpine linux
2024-08-24 16:30:43 +08:00
Donald Zou 79c03db9a0 Update 2024-08-24 16:27:02 +08:00
Noxcis 0c77823020
Update main.yml 2024-08-24 03:00:51 -05:00
Noxcis deed7e0022
Update main.yml 2024-08-24 02:56:40 -05:00
Noxcis 99db8c7335
Update main.yml 2024-08-24 02:53:30 -05:00
Noxcis 9fe2aa9ed5
Update main.yml 2024-08-24 02:49:51 -05:00
Noxcis 4c80dc256b
Update main.yml 2024-08-24 02:46:43 -05:00
Noxcis cafe9e9c11
Update main.yml 2024-08-24 02:41:57 -05:00
Noxcis 27ff4e63b6
Update main.yml 2024-08-24 02:38:10 -05:00
Noxcis 8020714e07
Update main.yml 2024-08-24 02:33:56 -05:00
Noxcis dbd825ba4b
Update main.yml 2024-08-24 02:28:35 -05:00
Noxcis fee6cf29eb
Update main.yml 2024-08-24 02:26:30 -05:00
Noxcis 56287d8e7a
Update main.yml 2024-08-24 02:18:21 -05:00
Noxcis 45504eaf95
Update main.yml 2024-08-24 02:15:41 -05:00
Noxcis b8a9b1150a
Update main.yml 2024-08-24 02:13:04 -05:00
Donald Zou 8bd0e43f58 Update wgd.sh 2024-08-24 15:12:28 +08:00
Noxcis 2c83e9e83c
Update main.yml 2024-08-24 02:06:57 -05:00
Noxcis 53c9ca10a7
Update main.yml 2024-08-24 02:04:46 -05:00
Noxcis 75fbdac42e
Update main.yml 2024-08-24 01:40:34 -05:00
Noxcis 09d54546ca
Update main.yml 2024-08-24 01:34:03 -05:00
Noxcis b62fece3d0
Update main.yml 2024-08-24 01:29:51 -05:00
Noxcis 284a2b7f64
Update main.yml 2024-08-24 01:25:50 -05:00
Noxcis 9c873ccbbd
Update main.yml 2024-08-24 01:22:37 -05:00
Noxcis 5f72f90031
Update main.yml 2024-08-24 01:18:49 -05:00
Noxcis 93cf3c69b8
Update main.yml 2024-08-24 01:04:28 -05:00
Noxcis 88f856cbc7
Create DockerScout.yml 2024-08-24 01:01:56 -05:00
Noxcis 2d5796d161 Added Auto Config Creation
Reimplemented Automatic Wireguard Configuration Generation

Setting global Env Vars via the docker image build is still insecure, better to pass to dashboard before init.
2024-08-23 16:49:54 -05:00
Dselen 1d20dc9fcb Looking like a promising end of this task. 2024-08-23 13:01:50 -05:00
Dselen 49502235b5 These needed to be added. 2024-08-23 12:48:33 -05:00
Dselen 6e9d71fcf8 Added reverted some no longer needed changes 2024-08-23 12:48:16 -05:00
Dselen 27c7e33773 added RHEL etc workings. 2024-08-23 12:27:41 -05:00
Dselen 3012619049 testing 2024-08-23 11:58:14 -05:00
dselen d6801966c4
Merge branch 'donaldzou:main' into main 2024-08-23 16:19:58 +02:00
Dselen 518e29118c Reoganise the documents and added experimental Alpine Linux support in wgd.sh 2024-08-23 07:46:41 -05:00
Noxcis acf4f3fbf0 Merge branch 'main' of https://github.com/NOXCIS/WGDashboard 2024-08-22 23:16:46 -05:00
Noxcis 8378030c70 Fixed Docker Vulnerability
+ Switched Base Image to Alpine
+ Simplified Docker Build
+ Added Alpine support to wgd.sh script.
+ Maintained Project Layout.
2024-08-22 23:15:39 -05:00
Donald Zou dc7140d486
Update README.md 2024-08-23 09:24:18 +08:00
Dselen e3771a1c53 Refined logging output a bit. 2024-08-22 16:58:29 -05:00
Dselen 2e9ac00a42 modified all and patched security vulnerability issue #333 2024-08-22 16:31:47 -05:00
Dselen 4b8b3acd39 Small readme change 2024-08-22 14:00:55 -05:00
Dselen 8703798ca0 Modified all files and have a working product, awaiting feedback! 2024-08-22 13:38:29 -05:00
dselen 47ac438844
Update README.md
Rearranged Ubuntu match other descending formats.
2024-08-22 16:24:07 +02:00
Dselen bd3aa28523 Changed readme and compose for templating. 2024-08-20 14:48:36 -05:00
Dselen 68d0ae4002 Added context and refined code. 2024-08-20 13:54:49 -05:00
Dselen 6991039640 Working prototype. 2024-08-20 12:58:30 -05:00
Dselen 00611ef9dc Progress so far. 2024-08-20 09:58:25 -05:00
Donald Zou 3c50e4768a
Merge pull request #317 from donaldzou/v4.0-fix3
Fixed recursive use of cursor
2024-08-20 00:02:46 -07:00
Donald Zou 63dc9834be Fixed recursive use of cursor 2024-08-20 00:02:00 -07:00
Donald Zou f042e42633 Adjust version to v4.0.2 2024-08-19 21:30:47 -04:00
Donald Zou 39b80a2e7e Fixed the issue where updating is not working 2024-08-19 19:17:07 -04:00
Donald Zou fb6e948358 Update production UI 2024-08-19 16:55:18 -04:00
Donald Zou 181b0845bf
Merge pull request #313 from donaldzou/v4.0-fix2
Fixed #312, #311
2024-08-19 16:50:48 -04:00
Donald Zou b2a82dcfe5 Fixed #312, #311
- Fixed issue in #312: The dashboard will automatically get the actual Dashboard version number.
- Fixed issue in #311: WGDashboard was not treating restricted peers correctly.
2024-08-19 16:50:00 -04:00
Noxcis ed1c05dec9 Merge branch 'main' of https://github.com/NOXCIS/WGDashboard 2024-08-18 21:34:42 -05:00
Donald Zou cd73aef0c9
Merge pull request #307 from donaldzou/donaldzou-patch-3
Update README.md
2024-08-17 20:34:29 -04:00
Donald Zou ed522ec024
Update README.md 2024-08-17 20:34:20 -04:00
Noxcis a4151800f1
Update requirements.txt 2023-12-05 04:47:40 -08:00
Noxcis 932f24c966
Update dashboard.py 2023-12-05 04:44:01 -08:00
466 changed files with 42958 additions and 9038 deletions

View File

@ -1,61 +0,0 @@
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "antonioag95",
"name": "antonioag95",
"avatar_url": "https://avatars.githubusercontent.com/u/30556866?v=4",
"profile": "https://github.com/antonioag95",
"contributions": [
"test",
"code"
]
},
{
"login": "tonjo",
"name": "tonjo",
"avatar_url": "https://avatars.githubusercontent.com/u/4726289?v=4",
"profile": "https://github.com/tonjo",
"contributions": [
"code"
]
},
{
"login": "reafian",
"name": "Richard Newton",
"avatar_url": "https://avatars.githubusercontent.com/u/11992416?v=4",
"profile": "https://github.com/reafian",
"contributions": [
"code"
]
},
{
"login": "davejlong",
"name": "David Long",
"avatar_url": "https://avatars.githubusercontent.com/u/175317?v=4",
"profile": "http://www.davejlong.com",
"contributions": [
"code"
]
},
{
"login": "marneu",
"name": "Markus Neubauer",
"avatar_url": "https://avatars.githubusercontent.com/u/5978293?v=4",
"profile": "http://www.std-soft.com",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "WGDashboard",
"projectOwner": "donaldzou",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true
}

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
.git
.github
*.md
tests/
docs/

4
.github/FUNDING.yml vendored
View File

@ -1,4 +1,4 @@
# These are supported funding model platforms
github: [donaldzou]
patreon: DonaldDonnyZou
github: [WGDashboard]
open_collective: wgdashboard

31
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,31 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip"
directory: "/src"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/src/static/app"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/.github"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "/docker"
schedule:
interval: "weekly"
- package-ecosystem: "docker-compose"
directory: "/docker"
schedule:
interval: "weekly"

View File

@ -12,6 +12,7 @@
name: "CodeQL"
on:
workflow_dispatch:
push:
branches: [ main ]
pull_request:
@ -38,11 +39,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -53,7 +54,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v4
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -67,4 +68,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v4

106
.github/workflows/docker-debug.yml vendored Normal file
View File

@ -0,0 +1,106 @@
name: MANUAL Docker workflow for debugging
on:
workflow_dispatch:
env:
DOCKERHUB_PREFIX: docker.io
GITHUB_CONTAINER_PREFIX: ghcr.io
DOCKER_IMAGE: WGDashboard
jobs:
docker_build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_PREFIX }}
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_CONTAINER_PREFIX }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/amd64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract Docker metadata from environment
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.DOCKERHUB_PREFIX }}/donaldzou/${{ env.DOCKER_IMAGE }}
${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=short,prefix=
- name: Build and export Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64
docker_scan:
if: ${{ github.event_name != 'pull_request' }}
runs-on: ubuntu-latest
needs: docker_build
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_PREFIX }}
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_CONTAINER_PREFIX }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Scout CVEs
uses: docker/scout-action@v1
with:
command: cves
image: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:main
only-severities: critical,high
only-fixed: true
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
exit-code: true
- name: Docker Scout Compare
uses: docker/scout-action@v1
with:
command: compare
# Set to Github for maximum compat
image: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:main
to: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:latest
only-severities: critical,high
ignore-unchanged: true
github-token: ${{ secrets.GITHUB_TOKEN }}

116
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,116 @@
name: Docker Build and Push
on:
workflow_dispatch:
push:
branches:
- 'main'
tags:
- '*'
paths:
- 'src/**'
- 'docker/**'
release:
types: [ published ]
env:
DOCKERHUB_PREFIX: docker.io
GITHUB_CONTAINER_PREFIX: ghcr.io
DOCKER_IMAGE: WGDashboard
jobs:
docker_build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_PREFIX }}
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_CONTAINER_PREFIX }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract Docker metadata from environment
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.DOCKERHUB_PREFIX }}/donaldzou/${{ env.DOCKER_IMAGE }}
${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=short,prefix=
- name: Build and export Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
docker_scan:
if: ${{ github.event_name != 'pull_request' }}
runs-on: ubuntu-latest
needs: docker_build
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_PREFIX }}
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.GITHUB_CONTAINER_PREFIX }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Scout CVEs
uses: docker/scout-action@v1
with:
command: cves
image: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:main
only-severities: critical,high
only-fixed: true
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
exit-code: true
- name: Docker Scout Compare
uses: docker/scout-action@v1
with:
command: compare
# Set to Github for maximum compat
image: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:main
to: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:latest
only-severities: critical,high
ignore-unchanged: true
github-token: ${{ secrets.GITHUB_TOKEN }}

26
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,26 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
workflow_dispatch:
schedule:
- cron: '00 08 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has not been updated for 20 days'
stale-pr-message: 'This pull request has not been updated for 20 days'
stale-issue-label: 'stale'
exempt-issue-labels: 'enhancement,ongoing'
days-before-stale: 20

6
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.tar
.vscode
.DS_Store
.idea
@ -10,7 +11,7 @@ src/static/pic.xd
*.conf
private_key.txt
public_key.txt
venv/**
*venv*
log/**
release/*
src/db/wgdashboard.db
@ -18,6 +19,9 @@ src/db/wgdashboard.db
node_modules/**
*/proxy.js
src/static/app/proxy.js
.secrets
*.ini
*.pid
# Logs
logs

629
README.md
View File

@ -1,566 +1,85 @@
<p align="center">
<img alt="WGDashboard" src="./src/static/img/logo.png" width="128">
</p>
<h1 align="center">WGDashboard</h1>
> [!TIP]
> 🎉 To help us better understand and improve WGDashboards performance, were launching the **WGDashboard Testing Program**. As part of this program, participants will receive free WireGuard VPN access to our server in Toronto, Canada, valid for **24 hours** or up to **1GB of total traffic**—whichever comes first. If youd like to join, visit [https://wg.wgdashboard.dev/](https://wg.wgdashboard.dev/) for more details!
![](https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Posters/Banner.png)
<p align="center">
<img src="https://forthebadge.com/images/badges/made-with-python.svg">
<img src="https://forthebadge.com/images/badges/made-with-javascript.svg">
<img src="https://forthebadge.com/images/badges/license-mit.svg">
<img alt="WGDashboard" src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Logos/Logo-2-Rounded-512x512.png" width="128">
</p>
<h1 align="center">
<a href="https://wgdashboard.dev">WGDashboard</a>
</h1>
<p align="center">
<img src="https://img.shields.io/badge/Made_With-Python-blue?style=for-the-badge&logo=python&logoColor=ffffff">
<img src="https://img.shields.io/badge/Made_With-Vue.js-42b883?style=for-the-badge&logo=vuedotjs&logoColor=ffffff">
<img src="https://img.shields.io/badge/License-Apache_License_2.0-D22128?style=for-the-badge&logo=apache&logoColor=ffffff">
</p>
<p align="center">
<a href="https://github.com/WGDashboard/WGDashboard/releases/latest"><img src="https://img.shields.io/github/v/release/donaldzou/wireguard-dashboard?style=for-the-badge"></a>
<a href="https://wakatime.com/badge/github/donaldzou/WGDashboard"><img src="https://wakatime.com/badge/user/45f53c7c-9da9-4cb0-85d6-17bd38cc748b/project/5334ae20-e9a6-4c55-9fea-52d4eb9dfba6.svg?style=for-the-badge" alt="wakatime"></a>
<a href="https://hitscounter.dev"><img src="https://hitscounter.dev/api/hit?url=https%3A%2F%2Fgithub.com%2Fdonaldzou%2FWGDashboard&label=Visitor&icon=github&color=%230a58ca&style=for-the-badge"></a>
<img src="https://img.shields.io/docker/pulls/donaldzou/wgdashboard?logo=docker&label=Docker%20Image%20Pulls&labelColor=ffffff&style=for-the-badge">
<img src="https://github.com/WGDashboard/WGDashboard/actions/workflows/docker.yml/badge.svg?style=for-the-badge">
<img src="https://github.com/WGDashboard/WGDashboard/actions/workflows/codeql-analyze.yaml/badge.svg">
</p>
<p align="center"><b>This project is supported by</b></p>
<p align="center">
<a href="https://m.do.co/c/a84cb9aac585">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
</a>
</p>
<p align="center">Monitoring WireGuard is not convenient, in most case, you'll need to login to your server and type <code>wg show</code>. That's why this project is being created, to view and manage all WireGuard configurations in an easy way.</p>
<p align="center">Though all these awesome features are present, we are still striving to make it <b>easy to install and use</b></p>
<p align="center"><b><i>This project is not affiliated to the official WireGuard Project</i></b></p>
<h3 align="center">Looking for help or want to chat about this project?</h4>
<p align="center">
You can reach out at
</p>
<p align="center">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
<a align="center" href="https://discord.gg/72TwzjeuWm" target="_blank"><img src="https://img.shields.io/discord/1276818723637956628?labelColor=ffffff&style=for-the-badge&logo=discord&label=Discord"></a>
<a align="center" href="https://www.reddit.com/r/WGDashboard/" target="_blank"><img src="https://img.shields.io/badge/Reddit-r%2FWGDashboard-FF4500?style=for-the-badge&logo=reddit"></a>
<a align="center" href="https://app.element.io/#/room/#wgd:matrix.org" target="_blank"><img src="https://img.shields.io/badge/Matrix_Chatroom-%23WGD-000000?style=for-the-badge&logo=matrix"></a>
</p>
<h3 align="center">Want to support this project?</h4>
<p align="center">
You can support via <br>
</p>
<p align="center">
<a href="https://github.com/donaldzou/wireguard-dashboard/releases/latest"><img src="https://img.shields.io/github/v/release/donaldzou/wireguard-dashboard"></a>
<a href="https://wakatime.com/badge/github/donaldzou/WGDashboard"><img src="https://wakatime.com/badge/github/donaldzou/WGDashboard.svg" alt="wakatime"></a>
<a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fdonaldzou%2FWGDashboard&count_bg=%2379C83D&title_bg=%23555555&icon=github.svg&icon_color=%23E7E7E7&title=Visitor&edge_flat=false"/></a>
<a align="center" href="https://github.com/sponsors/donaldzou" target="_blank"><img src="https://img.shields.io/badge/GitHub%20Sponsor-2e9a40?style=for-the-badge&logo=github"></a>
<a align="center" href="https://buymeacoffee.com/donaldzou" target="_blank"><img src="https://img.shields.io/badge/Buy%20me%20a%20coffee-ffdd00?style=for-the-badge&logo=buymeacoffee&logoColor=000000"></a>
<a align="center" href="https://patreon.com/c/DonaldDonnyZou/membership" target="_blank"><img src="https://img.shields.io/badge/Patreon-000000?style=for-the-badge&logo=patreon&logoColor=ffffff"></a>
</p>
<p align="center">Monitoring WireGuard is not convenient, need to remote access to server and type <code>wg show</code>. That's why this project is being created, to view all configurations and manage them in a easy way.</p>
<p align="center">With all these awesome features, while keeping it <b>simple</b>, <b>easy to install and use</b></p>
<p align="center"><b><i>This project is not affiliate to the official WireGuard Project</i></b></p>
## 📣 What's New: v4.0
### 🎉 New Features
- **Updated dashboard design**: Re-designed some of the section with more modern style and layout, the UI is faster and more responsive, it also uses less memory. But overall is still the same dashboard you're familiarized.
- **Docker Solution**: We now have 2 docker solutions! Thanks to @DaanSelen & @shuricksumy for providing them. For more information, please see the [Docker](#-docker-solutions) section below.
- **Peer Job Scheduler**: Now you can schedule jobs for each peer to either **restrict** or **delete** the peer if the peer's total / upload / download data usage exceeded a limit, or you can set a specific datetime to restrict or delete the peer.
- **Share Peer's QR Code with Public Link**: You can share a peer's QR code and `.conf` file without the need to loging in.
- **WGDashboard's REST API**: You can now request all the api endpoint used in the dashboard. For more details please review the [API Documentation](./docs/api-documents.md).
- **Logging**: Dashboard will now log all activity on the dashboard and API requests.
- **Time-Based One-Time Password (TOTP)**: You can enable this function to add one more layer of security, and generate the TOTP with your choice of authenticator.
- **Designs**
- **Real-time Graphs**: You can view real-time data changes with graphs in each configuration.
- **Night mode**: You know what that means, it avoids bugs ;)
- **Enforce Python Virtual Environment**: I noticed newer Python version (3.12) does not allow to install packages globally, and plus I think is a good idea to use venv.
### 🧐 Other Changes
- **Deprecated jQuery from the project, and migrated and rewrote the whole front-end with Vue.js. This allows the dashboard is future proofed, and potential cross server access with a desktop app.**
- Rewrote the backend into a REST API structure
- Improved SQL query efficient
- Removed all templates, except for `index.html` where it will load the Vue.js app.
- Parsing names in `.conf`
- Minimized the need to read `.conf`, only when any `.conf` is modified
### 🥘 New Experimental Features
- **Cross-Server Access**: Now you can access other servers that installed `v4` of WGDashboard through API key.
- **Desktop App**: Thanks to **Cross-Server Access**, you can now download an ElectronJS based desktop app of WGDashboard, and use that to access WGDashboard on different servers.
- > For more information, please scroll down to [🥘 Experimental Functions](#-experimental-functions)
> I can't thank enough for all of you who wait for this release, and for those who are new to this project, welcome :)
> Also, huge thanks to who contributed to this major release:
> @bolgovrussia, @eduardorosabales, @Profik, @airgapper, @tokon2000, @bkeenke, @kontorskiy777, @bugsse, @Johnnykson, @DaanSelen, @shuricksumy and many others!
<p align="center">
<b>or, visit our merch store and support us by purchasing a merch for only $USD 17.00 (Including shipping worldwide & duties)</b>
</p>
<p align="center">
<a align="center" href="https://merch.wgdashboard.dev" target="_blank"><img src="https://img.shields.io/badge/Merch%20from%20WGDashboard-926183?style=for-the-badge"></a>
</p>
<hr>
## 📋 Table of Content
<!-- TOC -->
* [📣 What's New: v4.0](#-whats-new-v40)
* [🎉 New Features](#-new-features)
* [🧐 Other Changes](#-other-changes)
* [🥘 New Experimental Features](#-new-experimental-features)
* [📋 Table of Content](#-table-of-content)
* [💡 Features](#-features)
* [📝 Requirements](#-requirements)
* [Supported Operating Systems](#supported-operating-systems)
* [Existing WireGuard Configurations](#existing-wireguard-configurations)
* [🛠 Install](#-install)
* [Install Commands](#install-commands)
* [Ubuntu 20.04 LTS](#ubuntu-2004-lts)
* [Ubuntu 22.04 LTS & Ubuntu 24.02 LTS](#ubuntu-2204-lts--ubuntu-2402-lts)
* [Debian 12.6](#debian-126)
* [Debian 11.10](#debian-1110)
* [Red Hat Enterprise Linux 9.4 & CentOS 9-Stream](#red-hat-enterprise-linux-94--centos-9-stream)
* [Fedora 40 & Fedora 39 & Fedora 38](#fedora-40--fedora-39--fedora-38)
* [Manual Installation](#manual-installation)
* [🪜 Usage](#-usage)
* [Start/Stop/Restart WGDashboard](#startstoprestart-wgdashboard)
* [Autostart WGDashboard on boot (>= v2.2)](#autostart-wgdashboard-on-boot--v22)
* [✂️ Dashboard Configuration](#-dashboard-configuration)
* [Dashboard Configuration file](#dashboard-configuration-file)
* [Generating QR code and peer configuration file (.conf)](#generating-qr-code-and-peer-configuration-file-conf)
* [❓ How to update the dashboard?](#-how-to-update-the-dashboard)
* [**Please note for users who are using `v3 - v3.0.6` want to update to `v4.0`**](#please-note-for-users-who-are-using-v3---v306-want-to-update-to-v40)
* [**Please note for users who are using `v2.3.1` or below**](#please-note-for-users-who-are-using-v231-or-below)
* [🐬 Docker Solutions](#-docker-solutions)
* [Solution 1 from @DaanSelen](#solution-1-from-daanselen)
* [Solution 2 from @shuricksumy](#solution-2-from-shuricksumy)
* [📖 WGDashboard REST API Documentation & How to use API Key](#-wgdashboard-rest-api-documentation--how-to-use-api-key)
* [🥘 Experimental Features](#-experimental-features)
* [Cross-Server Access](#cross-server-access)
* [Desktop App](#desktop-app)
* [🔍 Screenshot](#-screenshot)
* [🕰️ Changelogs](#-changelogs)
<!-- TOC -->
## 💡 Features
- Automatically look for existing WireGuard configuration under `/etc/wireguard`
- Easy to use interface, provided credential and TOTP protection to the dashboard
- Manage peers and configuration
- Add Peers or by bulk with auto-generated information
- Edit peer information
- Delete peers with ease
- Restrict peers
- Generate QR Code and `.conf` file for peers, share it through a public link
- Schedule jobs to delete / restrict peer when conditions are met
- View real time peer status
- Testing tool: Ping and Traceroute to your peer
## 📝 Requirements
1. Supported operating systems. Please view the list below.
2. WireGuard & WireGuard-Tools (`wg-quick`)
3. Python 3.10 / 3.11 / 3.12
4. `git`, `net-tools`, `sudo` (_This should only apply to RHEL 9 & 8, interestingly it doesn't have it preinstalled)_
### Supported Operating Systems
> [!NOTE]
> All operating systems below are tested by myself. All are ARM64 ran in UTM Virtual Machine.
| Ubuntu | Debian | Red Hat Enterprise Linux | CentOS | Fedora |
|-----------|--------|--------------------------|----------|--------|
| 20.04 LTS | 12.6 | 9.4 | 9-Stream | 40 |
| 22.04 LTS | 11.10 | | | 39 |
| 24.02 LTS | | | | 38 |
> [!TIP]
> If you installed WGDashboard on other systems without any issues, please let me know. Thank you!
### Existing WireGuard Configurations
> [!NOTE]
> This only applies to existing WireGuard Configuration under `/etc/wireguard`
```ini
[Interface]
...
SaveConfig = true
# Need to include this line to allow WireGuard Tool to save your configuration,
# or if you just want it to monitor your WireGuard Interface and don't need to
# make any changes with the dashboard, you can set it to false.
[Peer]
#Name# = Donald's iPhone
PublicKey = abcd1234
AllowedIPs = 1.2.3.4/32
```
> [!TIP]
> With `v4`, WGDashboard will look for entry with `#Name# = abc...` in each peer and use that for the name.
## 🛠 Install
### Install Commands
These commands are tested by myself in each OS. It contains commands to install WireGuard, Git, Net Tools, and even Python on some OS.
> [!WARNING]
> Please make sure you understand these commands before you run them.
#### Ubuntu 20.04 LTS
```shell
sudo add-apt-repository ppa:deadsnakes/ppa -y && \
sudo apt-get update -y && \
sudo apt-get install python3.10 python3.10-distutils wireguard-tools net-tools --no-install-recommends -y && \
git clone https://github.com/donaldzou/WGDashboard.git && \
cd WGDashboard/src && \
chmod +x ./wgd.sh && \
./wgd.sh install && \
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
sudo sysctl -p
```
#### Ubuntu 22.04 LTS & Ubuntu 24.02 LTS
```shell
sudo apt-get update -y && \
sudo apt install wireguard-tools net-tools --no-install-recommends -y && \
git clone https://github.com/donaldzou/WGDashboard.git && \
cd ./WGDashboard/src && \
chmod +x ./wgd.sh && \
./wgd.sh install && \
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
sudo sysctl -p /etc/sysctl.conf
```
#### Debian 12.6
```shell
apt-get install sudo git iptables -y && \
sudo apt-get update && \
sudo apt install wireguard-tools net-tools && \
git clone https://github.com/donaldzou/WGDashboard.git && \
cd ./WGDashboard/src && \
chmod +x ./wgd.sh && \
./wgd.sh install && \
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
sudo sysctl -p /etc/sysctl.conf
```
#### Debian 11.10
> [!WARNING]
> This commands will download Python 3.10's source code and build from it, since Debian 11.10 doesn't comes with Python 3.10
```shell
apt-get install sudo -y && \
sudo apt-get update && \
sudo apt install -y git iptables build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev wireguard-tools net-tools && \
wget https://www.python.org/ftp/python/3.10.0/Python-3.10.0.tgz && \
tar -xvf Python-3.10.0.tgz && \
cd Python-3.10.0 && \
sudo ./configure --enable-optimizations && \
sudo make && \
sudo make altinstall && \
cd .. && \
git clone https://github.com/donaldzou/WGDashboard.git && \
cd ./WGDashboard/src && \
chmod +x ./wgd.sh && \
./wgd.sh install && \
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
sudo sysctl -p /etc/sysctl.conf
```
#### Red Hat Enterprise Linux 9.4 & CentOS 9-Stream
```shell
sudo yum install wireguard-tools net-tools git python3.11 -y && \
git clone https://github.com/donaldzou/WGDashboard.git && \
cd ./WGDashboard/src && \
chmod +x ./wgd.sh && \
./wgd.sh install && \
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
sudo sysctl -p /etc/sysctl.conf && \
firewall-cmd --add-port=10086/tcp --permanent && \
firewall-cmd --add-port=51820/udp --permanent && \
firewall-cmd --reload
```
#### Fedora 40 & Fedora 39 & Fedora 38
```shell
sudo yum install wireguard-tools net-tools git -y && \
git clone https://github.com/donaldzou/WGDashboard.git && \
cd ./WGDashboard/src && \
chmod +x ./wgd.sh && \
./wgd.sh install && \
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
sudo sysctl -p /etc/sysctl.conf && \
firewall-cmd --add-port=10086/tcp --permanent && \
firewall-cmd --add-port=51820/udp --permanent && \
firewall-cmd --reload
```
### Manual Installation
> [!NOTE]
> To ensure a smooth installation process, please make sure Python 3.10/3.11/3.12, `git`, `wireguard-tools` and `net-tools` are installed :)
1. Download WGDashboard
```shell
git clone https://github.com/donaldzou/WGDashboard.git wgdashboard
2. Open the WGDashboard folder
```shell
cd wgdashboard/src
```
3. Install WGDashboard
```shell
sudo chmod u+x wgd.sh && \
sudo ./wgd.sh install
```
4. Give read and execute permission to root of the WireGuard configuration folder, you can change the path if your configuration files are not stored in `/etc/wireguard`
```shell
sudo chmod -R 755 /etc/wireguard
```
5. Run WGDashboard
```shell
sudo ./wgd.sh start
```
6. Access dashboard
Access your server with port `10086` (e.g. http://your_server_ip:10086), using username `admin` and password `admin`. See below how to change port and ip that the dashboard is running with.
## 🪜 Usage
#### Start/Stop/Restart WGDashboard
```shell
cd wgdashboard/src
-----------------------------
./wgd.sh start # Start the dashboard in background
-----------------------------
./wgd.sh debug # Start the dashboard in foreground (debug mode)
-----------------------------
./wgd.sh stop # Stop the dashboard
-----------------------------
./wgd.sh restart # Restart the dasboard
```
#### Autostart WGDashboard on boot (>= v2.2)
In the `src` folder, it contained a file called `wg-dashboard.service`, we can use this file to let our system to autostart the dashboard after reboot. The following guide has tested on **Ubuntu**, most **Debian** based OS might be the same, but some might not. Please don't hesitate to provide your system if you have tested the autostart on another system.
1. Changing the directory to the dashboard's directory
```shell
cd wgdashboard/src
```
2. Get the full path of the dashboard's directory
```shell
pwd
#Output: /root/wgdashboard/src
```
For this example, the output is `/root/wireguard-dashboard/src`, your path might be different since it depends on where you downloaded the dashboard in the first place. **Copy the the output to somewhere, we will need this in the next step.**
3. Edit the service file, the service file is located in `wireguard-dashboard/src`, you can use other editor you like, here will be using `nano`
```shell
nano wg-dashboard.service
```
You will see something like this:
```ini
[Unit]
After=syslog.target network-online.target
Wants=wg-quick.target
ConditionPathIsDirectory=/etc/wireguard
[Service]
Type=forking
PIDFile=<absolute_path_of_wgdashboard_src>/gunicorn.pid
WorkingDirectory=<absolute_path_of_wgdashboard_src>
ExecStart=<absolute_path_of_wgdashboard_src>/wgd.sh start
ExecStop=<absolute_path_of_wgdashboard_src>/wgd.sh stop
ExecReload=<absolute_path_of_wgdashboard_src>/wgd.sh restart
TimeoutSec=120
PrivateTmp=yes
Restart=always
[Install]
WantedBy=multi-user.target
```
Now, we need to replace all `<absolute_path_of_wgdashboard_src>` to the one you just copied from step 2. After doing this, the file will become something like this, your file might be different:
**Be aware that after the value of `WorkingDirectory`, it does not have a `/` (slash).** And then save the file after you edited it
4. Copy the service file to systemd folder
```bash
$ sudo cp wg-dashboard.service /etc/systemd/system/wg-dashboard.service
```
To make sure you copy the file successfully, you can use this command `cat /etc/systemd/system/wg-dashboard.service` to see if it will output the file you just edited.
5. Enable the service
```bash
$ sudo chmod 664 /etc/systemd/system/wg-dashboard.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable wg-dashboard.service
$ sudo systemctl start wg-dashboard.service # <-- To start the service
```
6. Check if the service run correctly
```bash
$ sudo systemctl status wg-dashboard.service
```
And you should see something like this
```shell
● wg-dashboard.service
Loaded: loaded (/etc/systemd/system/wg-dashboard.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2024-08-14 22:21:47 EDT; 55s ago
Process: 494968 ExecStart=/home/donaldzou/Wireguard-Dashboard/src/wgd.sh start (code=exited, status=0/SUCCESS)
Main PID: 495005 (gunicorn)
Tasks: 5 (limit: 4523)
Memory: 36.8M
CPU: 789ms
CGroup: /system.slice/wg-dashboard.service
├─495005 /home/donaldzou/Wireguard-Dashboard/src/venv/bin/python3 ./venv/bin/gunicorn --config ./gunicorn.conf.py
└─495007 /home/donaldzou/Wireguard-Dashboard/src/venv/bin/python3 ./venv/bin/gunicorn --config ./gunicorn.conf.py
Aug 14 22:21:40 wg sudo[494978]: root : PWD=/home/donaldzou/Wireguard-Dashboard/src ; USER=root ; COMMAND=./venv/bin/gunicorn --config ./gunicorn.conf.py
Aug 14 22:21:40 wg sudo[494978]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=0)
Aug 14 22:21:40 wg wgd.sh[494979]: [WGDashboard] WGDashboard w/ Gunicorn will be running on 0.0.0.0:10086
Aug 14 22:21:40 wg wgd.sh[494979]: [WGDashboard] Access log file is at ./log/access_2024_08_14_22_21_40.log
Aug 14 22:21:40 wg wgd.sh[494979]: [WGDashboard] Error log file is at ./log/error_2024_08_14_22_21_40.log
Aug 14 22:21:40 wg sudo[494978]: pam_unix(sudo:session): session closed for user root
Aug 14 22:21:45 wg wgd.sh[494968]: [WGDashboard] Checking if WGDashboard w/ Gunicorn started successfully
Aug 14 22:21:47 wg wgd.sh[494968]: [WGDashboard] WGDashboard w/ Gunicorn started successfully
Aug 14 22:21:47 wg wgd.sh[494968]: ------------------------------------------------------------
Aug 14 22:21:47 wg systemd[1]: Started wg-dashboard.service.
```
If you see `Active:` followed by `active (running) since...` then it means it run correctly.
7. Stop/Start/Restart the service
```bash
sudo systemctl stop wg-dashboard.service # <-- To stop the service
sudo systemctl start wg-dashboard.service # <-- To start the service
sudo systemctl restart wg-dashboard.service # <-- To restart the service
```
8. **And now you can reboot your system, and use the command at step 6 to see if it will auto start after the reboot, or just simply access the dashboard through your browser. If you have any questions or problem, please report it in the issue page.**
## ✂️ Dashboard Configuration
#### Dashboard Configuration file
Since version 2.0, WGDashboard will be using a configuration file called `wg-dashboard.ini`, (It will generate automatically after first time running the dashboard). More options will include in future versions, and for now it included the following configurations:
| | Description | Default | Edit Available |
|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|----------------|
| **`[Account]`** | *Configuration on account* | | |
| `username` | Dashboard login username | `admin` | Yes |
| `password` | Password, will be hash with SHA256 | `admin` hashed in SHA256 | Yes |
| | | | |
| **`[Server]`** | *Configuration on dashboard* | | |
| `wg_conf_path` | The path of all the Wireguard configurations | `/etc/wireguard` | Yes |
| `app_ip` | IP address the dashboard will run with | `0.0.0.0` | Yes |
| `app_port` | Port the the dashboard will run with | `10086` | Yes |
| `auth_req` | Does the dashboard need authentication to access, if `auth_req = false` , user will not be access the **Setting** tab due to security consideration. **User can only edit the file directly in system**. | `true` | **No** |
| `version` | Dashboard Version | `v4.0` | **No** |
| `dashboard_refresh_interval` | How frequent the dashboard will refresh on the configuration page | `60000ms` | Yes |
| `dashboard_sort` | How configuration is sorting | `status` | Yes |
| `dashboard_theme` | Dashboard Theme | `dark` | Yes |
| | | | |
| **`[Peers]`** | *Default Settings on a new peer* | | |
| `peer_global_dns` | DNS Server | `1.1.1.1` | Yes |
| `peer_endpoint_allowed_ip` | Endpoint Allowed IP | `0.0.0.0/0` | Yes |
| `peer_display_mode` | How peer will display | `grid` | Yes |
| `remote_endpoint` | Remote Endpoint (i.e where your peers will connect to) | *depends on your server's default network interface* | Yes |
| `peer_mtu` | Maximum Transmit Unit | `1420` | |
| `peer_keep_alive` | Keep Alive | `21` | Yes |
#### Generating QR code and peer configuration file (.conf)
Starting version 2.2, dashboard can now generate QR code and configuration file for each peer. Here is a template of what each QR code encoded with and the same content will be inside the file:
```ini
[Interface]
PrivateKey = QWERTYUIOPO234567890YUSDAKFH10E1B12JE129U21=
Address = 0.0.0.0/32
DNS = 1.1.1.1
[Peer]
PublicKey = QWERTYUIOPO234567890YUSDAKFH10E1B12JE129U21=
AllowedIPs = 0.0.0.0/0
Endpoint = 0.0.0.0:51820
```
| | Description | Default Value | Available in Peer setting |
| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------- |
| **`[Interface]`** | | | |
| `PrivateKey` | The private key of this peer | Private key generated by WireGuard (`wg genkey`) or provided by user | Yes |
| `Address` | The `allowed_ips` of your peer | N/A | Yes |
| `DNS` | The DNS server your peer will use | `1.1.1.1` - Cloud flare DNS, you can change it when you adding the peer or in the peer setting. | Yes |
| **`[Peer]`** | | | |
| `PublicKey` | The public key of your server | N/A | No |
| `AllowedIPs` | IP ranges for which a peer will route traffic | `0.0.0.0/0` - Indicated a default route to send all internet and VPN traffic through that peer. | Yes |
| `Endpoint` | Your wireguard server ip and port, the dashboard will search for your server's default interface's ip. | `<your server default interface ip>:<listen port>` | Yes |
## ❓ How to update the dashboard?
#### **Please note for users who are using `v3 - v3.0.6` want to update to `v4.0`**
- Although theoretically updating through `wgd.sh` should work, but I still suggest you to update the dashboard manually.
#### **Please note for users who are using `v2.3.1` or below**
- For user who is using `v2.3.1` or below, please notice that all data that stored in the current database will **not** transfer to the new database. This is hard decision to move from TinyDB to SQLite. But SQLite does provide a thread-safe access and TinyDB doesn't. I couldn't find a safe way to transfer the data, so you need to do them manually... Sorry about that :pensive:。 But I guess this would be a great start for future development :sunglasses:.
1. Change your directory to `wgdashboard`
```shell
cd wgdashboard/src
```
2. Update the dashboard
```shell
git pull https://github.com/donaldzou/WGDashboard.git v4.0 --force
```
3. Install
```shell
sudo ./wgd.sh install
```
Starting with `v3.0`, you can simply do `sudo ./wgd.sh update` !! (I hope)
## 🐬 Docker Solutions
Current, we have 2 beloved contributors provided solutions for hosting WGDashboard with Docker
### Solution 1 from @DaanSelen
Please visit [Docker-explain.md](./docker/Docker-explain.md)
### Solution 2 from @shuricksumy
Please visit [shuricksumy/docker-wgdashboard](https://github.com/shuricksumy/docker-wgdashboard)
> For questions or issues related to Docker, please visit [#272](https://github.com/donaldzou/WGDashboard/issues/272)
## 📖 WGDashboard REST API Documentation & How to use API Key
Please visit the [API Documentation](./docs/api-documents.md)
## 🥘 Experimental Features
### Cross-Server Access
Starting with `v4.0`, you can access WGDashboards on other server through one WGDashboard with API Keys
![Cross Server Example](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/cross-server.gif)
### Desktop App
Since the major changes for `v4.0` is to move the whole front-end code to Vue.js. And with this change, we can take the
advantage of combining ElectronJS and Vue.js to create a Desktop version of WGDashboard. Currently, we provide an Universal macOS app and a Windows app.
To download the app, please visit the [latest release](https://github.com/donaldzou/WGDashboard/releases).
![ElectronJS App Demo](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/electronjs-app.gif)
## 🔍 Screenshot
![Sign In](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/sign-in.png)
![Cross Server](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/cross-server.png)
![Index](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/index.png)
![New Configuration](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/new-configuration.png)
![Settings](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/settings.png)
![Light-Dark Mode](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/light-dark.png)
![Configuration](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/configuration.png)
![Add Peers](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/add-peers.png)
![Ping](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/ping.png)
![Traceroute](https://donaldzou.nyc3.cdn.digitaloceanspaces.com/wgdashboard-images/traceroute.png)
## 🕰️ Changelogs
Please visit the [Changelogs.md](./docs/changelogs.md)
<h4 align="center">
for more information, visit our
</h4>
<h1 align="center">
<a href="https://wgdashboard.dev">Official Website</a>
</h1>
# Screenshots
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/sign-in.png" alt=""/>
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/cross-server.png" alt=""/>
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/index.png" alt=""/>
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/new-configuration.png" alt="" />
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/settings.png" alt="" />
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/light-dark.png" alt="" />
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/configuration.png" alt=""/>
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/add-peers.png" alt="" />
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/ping.png" alt=""/>
<img src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/traceroute.png" alt=""/>

View File

@ -4,7 +4,5 @@
| Version | Supported |
| ------- | ------------------ |
| 5.1.x | :white_check_mark: |
| 5.0.x | :x: |
| 4.0.x | :white_check_mark: |
| < 4.0 | :x: |
| 4.3 | :white_check_mark: |
| < 4.3 | :x: |

View File

@ -0,0 +1,76 @@
FROM golang:1.24 AS awg-go
RUN git clone https://github.com/WGDashboard/amneziawg-go /awg
WORKDIR /awg
RUN go mod download && \
go mod verify && \
go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin
FROM alpine:latest AS awg-tools
RUN apk update && apk add --no-cache \
make git build-base linux-headers \
&& git clone https://github.com/WGDashboard/amneziawg-tools \
&& cd amneziawg-tools/src \
&& make \
&& chmod +x wg*
FROM alpine:latest
LABEL maintainer="dselen@nerthus.nl"
RUN apk update && apk add --no-cache \
iproute2 iptables bash curl wget unzip procps sudo \
tzdata wireguard-tools python3 py3-psutil py3-bcrypt openresolv
COPY --from=awg-go /usr/bin/amneziawg-go /usr/bin/amneziawg-go
COPY --from=awg-tools /amneziawg-tools/src/wg /usr/bin/awg
COPY --from=awg-tools /amneziawg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
# Declaring environment variables, change Peernet to an address you like, standard is a 24 bit subnet.
ARG wg_net="10.0.0.1" \
wg_port="51820"
# Following ENV variables are changable on container runtime because /entrypoint.sh handles that. See compose.yaml for more info.
ENV TZ="Europe/Amsterdam" \
global_dns="9.9.9.9" \
wgd_port="10086" \
public_ip=""
# Using WGDASH -- like wg_net functionally as a ARG command. But it is needed in entrypoint.sh so it needs to be exported as environment variable.
ENV WGDASH=/opt/wgdashboard
# Doing WireGuard Dashboard installation measures. Modify the git clone command to get the preferred version, with a specific branch for example.
RUN mkdir /data \
&& mkdir /configs \
&& mkdir -p ${WGDASH}/src \
&& mkdir -p /etc/amnezia/amneziawg
COPY ./src ${WGDASH}/src
# Generate basic WireGuard interface. Echoing the WireGuard interface config for readability, adjust if you want it for efficiency.
# Also setting the pipefail option, verbose: https://github.com/hadolint/hadolint/wiki/DL4006.
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN out_adapt=$(ip -o -4 route show to default | awk '{print $NF}') \
&& echo -e "[Interface]\n\
Address = ${wg_net}/24\n\
PrivateKey =\n\
PostUp = iptables -t nat -I POSTROUTING 1 -s ${wg_net}/24 -o ${out_adapt} -j MASQUERADE\n\
PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP\n\
PreDown = iptables -t nat -D POSTROUTING -s ${wg_net}/24 -o ${out_adapt} -j MASQUERADE\n\
PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP\n\
ListenPort = ${wg_port}\n\
SaveConfig = true\n\
DNS = ${global_dns}" > /configs/wg0.conf.template \
&& chmod 600 /configs/wg0.conf.template
# Defining a way for Docker to check the health of the container. In this case: checking the gunicorn process.
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD sh -c 'pgrep gunicorn > /dev/null && pgrep tail > /dev/null' || exit 1
# Copy the basic entrypoint.sh script.
COPY ./docker/entrypoint.sh /entrypoint.sh
# Exposing the default WireGuard Dashboard port for web access.
EXPOSE 10086
WORKDIR $WGDASH
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]

View File

@ -1,82 +0,0 @@
# WG-Dashboard Docker Explanation:
Author: DaanSelen<br>
This document delves into how the WG-Dashboard Docker container has been built.<br>
Of course there are two stages, one before run-time and one at/after run-time.<br>
The `Dockerfile` describes how the container image is made, and the `entrypoint.sh` is executed after running the container. <br>
In this example, WireGuard is integrated into the container itself, so it should be a run-and-go.<br>
For more details on the source-code specific to this Docker image, refer to the source files, they have lots of comments.
I have tried to embed some new features such as `isolated_peers` and interface startup on container-start (through `enable_wg0`).
<img src="https://raw.githubusercontent.com/donaldzou/WGDashboard/main/img/logo.png" alt="WG-Dashboard Logo" title="WG-Dashboard Logo" width="150" height="150" />
## Getting the container running:
To get the container running you either pull the image from the repository, at the moment: `repo.nerthus.nl/app/wireguard-dashboard:latest`.<br>
From there either use the environment variables describe below as parameters or use the Docker Compose file: `compose.yaml`.
An example of a simple command to get the container running is show below:<br>
```shell
docker run -d \
--name wireguard-dashboard \
--restart unless-stopped \
-e enable_wg0=true \
-e isolated_peers=true \
-p 10086:10086/tcp \
-p 51820:51820/udp \
--cap-add NET_ADMIN \
repo.nerthus.nl/app/wireguard-dashboard:latest
```
<br>
If you want to use Compose instead of a raw Docker command, refer to the example in the `compose.yaml` or the one pasted below:
<br><br>
```yaml
services:
wireguard-dashboard:
image: repo.nerthus.nl/app/wireguard-dashboard:latest
restart: unless-stopped
container_name: wire-dash
environment:
#- tz=
#- global_dns=
- enable_wg0=true
- isolated_peers=false
#- public_ip=
ports:
- 10086:10086/tcp
- 51820:51820/udp
volumes:
- conf:/etc/wireguard
- app:/opt/wireguarddashboard/app
cap_add:
- NET_ADMIN
volumes:
conf:
app:
```
If you want to customize the yaml, make sure the core stays the same, but for example volume PATHs can be freely changed.<br>
This setup is just generic and will use the Docker volumes.
## Working with the container and environment variables:
Once the container is running, the installation process is essentially the same as running it on bare-metal.<br>
So go to the assign TCP port in this case HTTP, like the default 10086 one in the example and log into the WEB-GUI.<br>
| Environment variable | Accepted arguments | Default value | Verbose |
| -------------- | ------- | ------- | ------- |
| tz | Europe/Amsterdam or any confirming timezone notation. | Europe/Amsterdam | Sets the timezone of the Docker container. This is to timesync the container to any other processes which would need it. |
| global_dns | Any IPv4 address, such as my personal recommendation: 9.9.9.9 (QUAD9) | 1.1.1.1 | Set the default DNS given to clients once they connect to the WireGuard tunnel (VPN).
| enable_wg0 | `true` or `false` | `false` | Enables or disables the starting of the WireGuard interface on container 'boot-up'.
| isolated_peers | `true` or `false` | `true` | For security the default is true, and it disables peers to ping or reach eachother, the WireGuard interface IS able to reach the peers (Done through `iptables`).
| public_ip | Any IPv4 (public recommended) address, such as the one returned by default | Default uses the return of `curl ifconfig.me` | To reach your VPN from outside your own network, you need WG-Dashboard to know what your public IP-address is, otherwise it will generate faulty config files for clients.
## Closing remarks:
For feedback please submit an issue to the repository. Or message dselen@nerthus.nl.

View File

@ -1,77 +1,143 @@
# Pull from small Debian stable image.
FROM debian:stable-slim
#
# AWG GOLANG BUILDING STAGE
# Base: Alpine
#
# Pull the current golang-alpine image.
FROM golang:1.25-alpine AS awg-go
# Install build-dependencies.
RUN apk add --no-cache \
git \
gcc \
musl-dev
# Standard working directory for WGDashboard
RUN mkdir -p /workspace && \
git clone https://github.com/WGDashboard/amneziawg-go /workspace/awg
# Enable CGO compilation for AmneziaWG
ENV CGO_ENABLED=1
# Change directory
WORKDIR /workspace/awg
# Compile the binaries
RUN go version && \
go mod download && \
go mod verify && \
go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin
#
# AWG TOOLS BUILDING STAGE
# Base: Alpine
#
FROM alpine:latest AS awg-tools
# Install needed dependencies.
RUN apk add --no-cache \
make \
git \
build-base \
linux-headers \
ca-certificates
# Get the workspace ready
RUN mkdir -p /workspace && \
git clone https://github.com/WGDashboard/amneziawg-tools /workspace/awg-tools
# Change directory
WORKDIR /workspace/awg-tools/src
# Compile and change permissions
RUN make && chmod +x wg*
#
# PIP DEPENDENCY BUILDING
# Base: Alpine
#
# Use the python-alpine image for building pip dependencies
FROM python:3.14-alpine AS pip-builder
ARG TARGETPLATFORM
# Add the build dependencies and create a Python virtual environment.
RUN apk add --no-cache \
build-base \
pkgconfig \
python3-dev \
postgresql-dev \
libffi-dev \
libpq \
linux-headers \
rust \
cargo \
&& mkdir -p /opt/wgdashboard/src \
&& python3 -m venv /opt/wgdashboard/src/venv
# Copy the requirements file into the build layer.
COPY ./src/requirements.txt /opt/wgdashboard/src
RUN if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
sed -i 's|psycopg\[binary\]|psycopg[c]|' /opt/wgdashboard/src/requirements.txt; \
fi; \
cat /opt/wgdashboard/src/requirements.txt
# Install the pip packages
RUN . /opt/wgdashboard/src/venv/bin/activate && \
pip3 install --upgrade pip && \
pip3 install -r /opt/wgdashboard/src/requirements.txt
#
# WGDashboard RUNNING STAGE
# Base: Alpine
#
# Running with the python-alpine image.
FROM python:3.14-alpine AS final
LABEL maintainer="dselen@nerthus.nl"
# Declaring environment variables, change Peernet to an address you like, standard is a 24 bit subnet.
ENV wg_net="10.0.0.1"
# wg_net is used functionally as an ARG for its environment variable nature, do not change unless you know what you are doing.
# Following ENV variables are changable on container runtime because /entrypoint.sh handles that. See compose.yaml for more info.
ENV tz="Europe/Amsterdam"
ENV global_dns="1.1.1.1"
ENV enable_wg0="false"
ENV isolated_peers="true"
ENV public_ip="0.0.0.0"
# Doing basic system maintenance. Change the timezone to the desired timezone.
RUN ln -sf /usr/share/zoneinfo/${tz} /etc/localtime
# Doing package management operations, such as upgrading
RUN apt-get update && apt-get upgrade -y \
&& apt-get install -y --no-install-recommends curl \
git \
iproute2 \
iptables \
iputils-ping \
openresolv \
procps \
python3 \
python3-pip \
python3-venv \
traceroute \
wireguard \
wireguard-tools \
&& apt-get remove linux-image-* --autoremove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Removing the Linux Image package to preserve space on the image, for this reason also deleting apt lists, to be able to install packages: run apt update.
# Using WGDASH -- like wg_net functionally as a ARG command. But it is needed in entrypoint.sh so it needs to be exported as environment variable.
ENV WGDASH=/opt/wireguarddashboard
RUN python3 -m venv ${WGDASH}/venv
# Doing WireGuard Dashboard installation measures. Modify the git clone command to get the preferred version, with a specific branch for example.
RUN . ${WGDASH}/venv/bin/activate \
&& git clone https://github.com/donaldzou/WGDashboard.git ${WGDASH}/app \
&& pip3 install -r ${WGDASH}/app/src/requirements.txt \
&& chmod +x ${WGDASH}/app/src/wgd.sh \
&& .${WGDASH}/app/src/wgd.sh install
# Set the volume to be used for persistency.
VOLUME /etc/wireguard
# Generate basic WireGuard interface. Echoing the WireGuard interface config for readability, adjust if you want it for efficiency.
# Also setting the pipefail option, verbose: https://github.com/hadolint/hadolint/wiki/DL4006.
# Install only the runtime dependencies
RUN apk add --no-cache \
iproute2 iptables \
bash curl procps openrc \
tzdata wireguard-tools envsubst
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN wg genkey | tee /etc/wireguard/wg0_privatekey \
&& echo "[Interface]" > /wg0.conf \
&& echo "SaveConfig = true" >> /wg0.conf \
&& echo "Address = ${wg_net}/24" >> /wg0.conf \
&& echo "PrivateKey = $(cat /etc/wireguard/wg0_privatekey)" >> /wg0.conf \
&& echo "PostUp = iptables -t nat -I POSTROUTING 1 -s ${wg_net}/24 -o $(ip -o -4 route show to default | awk '{print $NF}') -j MASQUERADE" >> /wg0.conf \
&& echo "PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP" >> /wg0.conf \
&& echo "PreDown = iptables -t nat -D POSTROUTING 1" >> /wg0.conf \
&& echo "PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP" >> /wg0.conf \
&& echo "ListenPort = 51820" >> /wg0.conf \
#&& echo "DNS = ${global_dns}" >> /wg0.conf \
&& rm /etc/wireguard/wg0_privatekey
# Defining a way for Docker to check the health of the container. In this case: checking the login URL.
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD curl -f http://localhost:10086/signin || exit 1
# Copy only the final binaries from the AWG builder stages
COPY --from=awg-go /usr/bin/amneziawg-go /usr/bin/amneziawg-go
COPY --from=awg-tools /workspace/awg-tools/src/wg /usr/bin/awg
COPY --from=awg-tools /workspace/awg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
# Copy the basic entrypoint.sh script.
COPY entrypoint.sh /entrypoint.sh
# Environment variables
ARG wg_net="10.0.0.1"
ARG wg_subn="24"
ARG wg_port="51820"
ENV TZ="Europe/Amsterdam" \
global_dns="9.9.9.9" \
wgd_port="10086" \
public_ip="" \
WGDASH=/opt/wgdashboard
# Exposing the default WireGuard Dashboard port for web access.
# Create directories needed for operation
RUN mkdir /data /configs -p ${WGDASH}/src /etc/amnezia/amneziawg
# Copy the venv and source files from local compiled locations or repos
COPY ./src ${WGDASH}/src
COPY --from=pip-builder /opt/wgdashboard/src/venv /opt/wgdashboard/src/venv
COPY ./docker/wg0.conf.template /tmp/wg0.conf.template
# Copy in the runtime script, essential.
COPY ./docker/entrypoint.sh /entrypoint.sh
# First WireGuard interface template
RUN export out_adapt=$(ip -o -4 route show to default | awk '{print $NF}') \
&& envsubst < /tmp/wg0.conf.template > /configs/wg0.conf.template \
&& chmod 600 /configs/wg0.conf.template \
&& cat /configs/wg0.conf.template
# Set a healthcheck to determine the container its health
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD sh -c 'pgrep gunicorn > /dev/null && pgrep tail > /dev/null' || exit 1
# Expose ports on the container
EXPOSE 10086
WORKDIR $WGDASH/src
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]

213
docker/README.md Normal file
View File

@ -0,0 +1,213 @@
# WGDashboard Docker Explanation:
Author: @DaanSelen<br>
This document delves into how the WGDashboard Docker container has been built.<br>
Of course there are two stages (simply said), one before run-time and one at/after run-time.<br>
The `Dockerfile` describes how the container image is made, and the `entrypoint.sh` is executed after the container is started. <br>
In this example, [WireGuard](https://www.wireguard.com/) is integrated into the container itself, so it should be a run-and-go(/out-of-the-box) experience.<br>
For more details on the source-code specific to this Docker image, refer to the source files, they have lots of comments.
<br>
<img
src="https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Logos/Logo-2-Rounded-512x512.png"
alt="WG-Dashboard Logo"
title="WG-Dashboard Logo"
style="display: block; margin: 0 auto;"
width="150"
height="150"
/>
<br>
To get the container running you either pull the pre-made image from a remote repository, there are 2 official options.<br>
- ghcr.io/wgdashboard/wgdashboard:<tag>
- docker.io/donaldzou/wgdashboard:<tag>
> tags should be either: latest, main, <version> or <commit-sha>.
From there either use the environment variables described below as parameters or use the Docker Compose file: `compose.yaml`.<br>
Be careful, the default generated WireGuard configuration file uses port 51820/udp. So make sure to use this port if you want to use it out of the box.<br>
Otherwise edit the configuration file in WGDashboard under `Configuration Settings` -> `Edit Raw Configuration File`.
> Otherwise you need to enter the container and edit: `/etc/wireguard/wg0.conf`.
# WGDashboard: 🐳 Docker Deployment Guide
To run the container, you can either pull the image from the Github Container Registry (ghcr.io), Docker Hub (docker.io) or build it yourself. The image is available at:
> `docker.io` is in most cases automatically resolved by the Docker application. Therefor you can ofter specify: `donaldzou/wgdashboard:latest`
### 🔧 Quick Docker Run Command
Here's an example to get it up and running quickly:
```bash
docker run -d \
--name wgdashboard \
--restart unless-stopped \
-p 10086:10086/tcp \
-p 51820:51820/udp \
--cap-add NET_ADMIN \
ghcr.io/wgdashboard/wgdashboard:latest
```
> ⚠️ The default WireGuard port is `51820/udp`. If you change this, update the `/etc/wireguard/wg0.conf` accordingly.
---
### 📦 Docker Compose Alternative
You can also use Docker Compose for easier configuration:
```yaml
services:
wgdashboard:
image: ghcr.io/wgdashboard/wgdashboard:latest
restart: unless-stopped
container_name: wgdashboard
ports:
- 10086:10086/tcp
- 51820:51820/udp
volumes:
- aconf:/etc/amnezia/amneziawg
- conf:/etc/wireguard
- data:/data
cap_add:
- NET_ADMIN
volumes:
aconf:
conf:
data:
```
> 📁 You can customize the **volume paths** on the host to fit your needs. The example above uses Docker volumes.
---
## 🔄 Updating the Container
Updating the WGDashboard container should be through 'The Docker Way' - by pulling the newest/newer image and replacing this old one.
---
## ⚙️ Environment Variables
| Variable | Accepted Values | Default | Example | Description |
| ------------------ | ---------------------------------------- | ----------------------- | --------------------- | ----------------------------------------------------------------------- |
| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. |
| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. |
| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NATd. |
| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. |
| `username` | Any nonempty string | `-` | `admin` | Username for the WGDashboard web interface account. |
| `password` | Any nonempty string | `-` | `s3cr3tP@ss` | Password for the WGDashboard web interface account (stored hashed). |
| `enable_totp` | `true`, `false` | `true` | `false` | Enable TOTPbased twofactor authentication for the account. |
| `wg_autostart` | Wireguard interface name | `false` | `true` | Autostart the WireGuard client when the container launches. |
| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. |
| `email_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. |
| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. |
| `email_username` | Any non-empty string | `-` | `user@example.com` | Username for SMTP authentication. |
| `email_password` | Any non-empty string | `-` | `app_password` | Password for SMTP authentication. |
| `email_from` | Valid email address | `-` | `noreply@example.com` | Email address used as the sender for notifications. |
| `email_template` | Path to template file | `-` | `your-template` | Custom template for email notifications. |
---
## 🔐 Port Forwarding Note
When using multiple WireGuard interfaces, remember to **open their respective ports** on the host.
Examples:
```yaml
# Individual mapping
- 51821:51821/udp
# Or port range
- 51820-51830:51820-51830/udp
```
> 🚨 **Security Tip:** Only expose ports you actually use.
---
## 🛠️ Building the Image Yourself
To build from source:
```bash
git clone https://github.com/WGDashboard/WGDashboard.git
cd WGDashboard
docker build . -f docker/Dockerfile -t yourname/wgdashboard:latest
```
Example output:
```shell
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
yourname/wgdashboard latest c96fd96ee3b3 42 minutes ago 314MB
```
---
## 🧱 Dockerfile Overview
Here's a brief overview of the Dockerfile stages used in the image build:
### 1. **Build Tools & Go Compilation**
```Dockerfile
FROM golang:1.24 AS compiler
WORKDIR /go
RUN apt-get update && apt-get install -y ...
RUN git clone ... && make
...
```
### 2. **Binary Copy to Scratch**
```Dockerfile
FROM scratch AS bins
COPY --from=compiler /go/amneziawg-go/amneziawg-go /amneziawg-go
...
```
### 3. **Final Alpine Container Setup**
```Dockerfile
FROM alpine:latest
COPY --from=bins ...
RUN apk update && apk add --no-cache ...
COPY ./src ${WGDASH}/src
COPY ./docker/entrypoint.sh /entrypoint.sh
...
EXPOSE 10086
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
```
---
## 🚀 Entrypoint Overview
### Major Functions:
- **`ensure_installation`**: Sets up the app, database, and Python environment.
- **`set_envvars`**: Writes `wg-dashboard.ini` and applies environment variables.
- **`start_core`**: Starts the main WGDashboard service.
- **`ensure_blocking`**: Tails the error log to keep the container process alive.
---
## ✅ Final Notes
- Use `docker logs wgdashboard` for troubleshooting.
- Access the web interface via `http://your-ip:10086` (or whichever port you specified in the compose).
- The first time run will auto-generate WireGuard keys and configs (configs are generated from the template).
## Closing remarks:
For feedback please submit an issue to the repository. Or message dselen@nerthus.nl.

View File

@ -1,23 +1,42 @@
services:
wireguard-dashboard:
image: repo.nerthus.nl/app/wireguard-dashboard:latest
wgdashboard:
# Since the github organisation we recommend the ghcr.io.
# Alternatively we also still push to docker.io under donaldzou/wgdashboard.
# Both share the exact same tags. So they should be interchangable.
image: ghcr.io/wgdashboard/wgdashboard:latest
# Make sure to set the restart policy. Because for a VPN its important to come back IF it crashes.
restart: unless-stopped
container_name: wire-dash
environment:
container_name: wgdashboard
# Environment variables can be used to configure certain values at startup. Without having to configure it from the dashboard.
# By default its all disabled, but uncomment the following lines to apply these. (uncommenting is removing the # character)
# Refer to the documentation on https://wgdashboard.dev/ for more info on what everything means.
#environment:
#- tz= # <--- Set container timezone, default: Europe/Amsterdam.
#- global_dns= # <--- Set global DNS address, default: 1.1.1.1.
- enable_wg0=true # <--- If true, wg0 will be started on container startup. default: false.
- isolated_peers=false # <--- When set to true, it disallows peers to talk to eachother, setting to false, allows it, default: true.
#- public_ip= # <--- Set public IP to ensure the correct one is chosen, defaulting to the IP give by ifconfig.me.
#- wgd_port= # <--- Set the port WGDashboard will use for its web-server.
# The following section, ports is very important for exposing more than one Wireguard/AmneziaWireguard interfaces.
# Once you create a new configuration and assign a port in the dashboard, don't forget to add it to the ports as well.
# Quick-tip: most Wireguard VPN tunnels use UDP. WGDashboard uses HTTP, so tcp.
ports:
- 10086:10086/tcp
- 51820:51820/udp
# Volumes can be configured however you'd like. The default is using docker volumes.
# If you want to use local paths, replace the path before the : with your path.
volumes:
- aconf:/etc/amnezia/amneziawg
- conf:/etc/wireguard
- app:/opt/wireguarddashboard/app
- data:/data
# Needed for network administration.
cap_add:
- NET_ADMIN
# The following configuration is linked to the above default volumes.
volumes:
aconf:
conf:
app:
data:

View File

@ -1,109 +1,274 @@
#!/bin/bash
echo "Starting the WireGuard Dashboard Docker container."
clean_up() {
# Cleaning out previous data such as the .pid file and starting the WireGuard Dashboard. Making sure to use the python venv.
echo "Looking for remains of previous instances..."
if [ -f "/opt/wireguarddashboard/app/src/gunicorn.pid" ]; then
echo "Found old .pid file, removing."
rm /opt/wireguarddashboard/app/src/gunicorn.pid
config_file="/data/wg-dashboard.ini"
runtime_pid=""
trap 'stop_service' SIGTERM
# Hash password with bcrypt
hash_password() {
${WGDASH}/src/venv/bin/python3 -c "import bcrypt; print(bcrypt.hashpw('$1'.encode(), bcrypt.gensalt(12)).decode())"
}
# Function to set or update section/key/value in the INI file
set_ini() {
local section="$1" key="$2" value="$3"
local current_value
# Add section if it doesn't exist
grep -q "^\[${section}\]" "$config_file" \
|| printf "\n[%s]\n" "${section}" >> "$config_file"
# Check current value if key exists
if grep -q "^[[:space:]]*${key}[[:space:]]*=" "$config_file"; then
current_value=$(grep "^[[:space:]]*${key}[[:space:]]*=" "$config_file" | cut -d= -f2- | xargs)
# Dont display actual value if it's a password field
if [[ "$key" == *"password"* ]]; then
if [ "$current_value" = "$value" ]; then
echo "- $key is already set correctly (value hidden)"
return 0
fi
sed -i "/^\[${section}\]/,/^\[/{s|^[[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|}" "$config_file"
echo "- Updated $key (value hidden)"
else
echo "No remains found, continuing."
if [ "$current_value" = "$value" ]; then
echo "- $key is already set correctly ($value)"
return 0
fi
sed -i "/^\[${section}\]/,/^\[/{s|^[[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|}" "$config_file"
echo "- Updated $key to: $value"
fi
else
sed -i "/^\[${section}\]/a ${key} = ${value}" "$config_file"
# Don't display actual value if it's a password field
if [[ "$key" == *"password"* ]]; then
echo "- Added new setting $key (value hidden)"
else
echo "- Added new setting $key: $value"
fi
fi
}
start_core() {
# This first step is to ensure the wg0.conf file exists, and if not, then its copied over from the ephemeral container storage.
if [ ! -f "/etc/wireguard/wg0.conf" ]; then
cp "/wg0.conf" "/etc/wireguard/wg0.conf"
echo "WireGuard interface file copied over."
stop_service() {
echo "[WGDashboard] Stopping WGDashboard..."
local max_rounds="10"
local round="0"
local runtime_pid=""
while true; do
round=$((round + 1))
if [[ -f ${WGDASH}/src/gunicorn.pid ]]; then
runtime_pid=$(cat ${WGDASH}/src/gunicorn.pid)
echo "Running as PID: ${runtime_pid}"
return 0
fi
if [[ $round -eq $max_rounds ]]; then
echo "Reached breaking point!"
return 1
fi
sleep 0.5s
done
kill $runtime_pid
exit 0
}
echo "------------------------- START ----------------------------"
echo "Starting the WGDashboard Docker container."
ensure_installation() {
echo "Quick-installing..."
# Make the wgd.sh script executable.
chmod +x "${WGDASH}"/src/wgd.sh
cd "${WGDASH}"/src || exit
# Github issue: https://github.com/donaldzou/WGDashboard/issues/723
echo "Checking for stale pids..."
if [[ -f ${WGDASH}/src/gunicorn.pid ]]; then
echo "Found stale pid, removing..."
rm ${WGDASH}/src/gunicorn.pid
fi
# Removing clear shell command from the wgd.sh script to enhance docker logging.
echo "Removing clear command from wgd.sh for better Docker logging."
sed -i '/clear/d' ./wgd.sh
# Create required directories and links
if [ ! -d "/data/db" ]; then
echo "Creating database dir"
mkdir -p /data/db
fi
if [ ! -d "${WGDASH}/src/db" ]; then
ln -s /data/db "${WGDASH}/src/db"
fi
if [ ! -f "${config_file}" ]; then
echo "Creating wg-dashboard.ini file"
touch "${config_file}"
fi
if [ ! -f "${WGDASH}/src/wg-dashboard.ini" ]; then
ln -s "${config_file}" "${WGDASH}/src/wg-dashboard.ini"
fi
# Setup WireGuard if needed
if [ -z "$(ls -A /etc/wireguard)" ]; then
cp -a "/configs/wg0.conf.template" "/etc/wireguard/wg0.conf"
echo "Setting a secure private key."
local privateKey
privateKey=$(wg genkey)
sed -i "s|^PrivateKey *=.*$|PrivateKey = ${privateKey}|g" /etc/wireguard/wg0.conf
echo "Done setting template."
else
echo "WireGuard interface file looks to already be existing."
fi
echo "Activating Python venv and executing the WireGuard Dashboard service."
. "${WGDASH}"/venv/bin/activate
cd "${WGDASH}"/app/src || return # If changing the directory fails (permission or presence error), then bash will exist this function, causing the WireGuard Dashboard to not be succesfully launched.
bash wgd.sh start
# The following section takes care of the firewall rules regarding the 'isolated_peers' feature, which allows or drops packets destined from the wg0 to the wg0 interface.
if [ "${isolated_peers,,}" = "false" ]; then
echo "Isolated peers disabled, adjusting."
sed -i '/PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP/d' /etc/wireguard/wg0.conf
sed -i '/PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP/d' /etc/wireguard/wg0.conf
elif [ "${isolated_peers,,}" = "true" ]; then
upblocking=$(grep -c "PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP" /etc/wireguard/wg0.conf)
downblocking=$(grep -c "PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP" /etc/wireguard/wg0.conf)
if [ "$upblocking" -lt 1 ] && [ "$downblocking" -lt 1 ]; then
echo "Isolated peers enabled, adjusting."
sed -i '/PostUp = iptables -t nat -I POSTROUTING 1 -s/a PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP' /etc/wireguard/wg0.conf
sed -i '/PreDown = iptables -t nat -D POSTROUTING 1 -s/a PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP' /etc/wireguard/wg0.conf
fi
fi
# The following section takes care of
if [ "${enable_wg0,,}" = "true" ]; then
echo "Preference for wg0 to be turned on found."
wg-quick up wg0
else
echo "Preference for wg0 to be turned off found."
echo "Existing wg0 configuration file found, using that."
fi
}
set_envvars() {
echo "Setting relevant variables for operation."
printf "\n------------- SETTING ENVIRONMENT VARIABLES ----------------\n"
# If the timezone is different, for example in North-America or Asia.
if [ "${tz}" != "$(cat /etc/timezone)" ]; then
echo "Changing timezone."
ln -sf /usr/share/zoneinfo/"${tz}" /etc/localtime
echo "${tz}" > /etc/timezone
# Check if config file is empty
if [ ! -s "${config_file}" ]; then
echo "Config file is empty. Creating initial structure."
fi
# Changing the DNS used for clients and the dashboard itself.
if [ "${global_dns}" != "$(grep "peer_global_dns = " /opt/wireguarddashboard/app/src/wg-dashboard.ini | awk '{print $NF}')" ]; then
echo "Changing default dns."
echo "Checking basic configuration:"
set_ini Peers peer_global_dns "${global_dns}"
#sed -i "s/^DNS = .*/DNS = ${global_dns}/" /etc/wireguard/wg0.conf # Uncomment if you want to have DNS on server-level.
sed -i "s/^peer_global_dns = .*/peer_global_dns = ${global_dns}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini
if [ -z "${public_ip}" ]; then
public_ip=$(curl -s ifconfig.me)
echo "Automatically detected public IP: ${public_ip}"
fi
# Setting the public IP of the WireGuard Dashboard container host. If not defined, it will trying fetching it using a curl to ifconfig.me.
if [ "${public_ip}" = "0.0.0.0" ]; then
default_ip=$(curl -s ifconfig.me)
echo "Trying to fetch the Public-IP using ifconfig.me: ${default_ip}"
set_ini Peers remote_endpoint "${public_ip}"
set_ini Server app_port "${wgd_port}"
sed -i "s/^remote_endpoint = .*/remote_endpoint = ${default_ip}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini
elif [ "${public_ip}" != "$(grep "remote_endpoint = " /opt/wireguarddashboard/app/src/wg-dashboard.ini | awk '{print $NF}')" ]; then
echo "Setting the Public-IP using given variable: ${public_ip}"
# Account settings - process all parameters
[[ -n "$username" ]] && echo "Configuring user account:"
# Basic account variables
[[ -n "$username" ]] && set_ini Account username "${username}"
sed -i "s/^remote_endpoint = .*/remote_endpoint = ${public_ip}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini
if [[ -n "$password" ]]; then
echo "- Setting password"
set_ini Account password "$(hash_password "${password}")"
fi
# Additional account variables
[[ -n "$enable_totp" ]] && set_ini Account enable_totp "${enable_totp}"
[[ -n "$totp_verified" ]] && set_ini Account totp_verified "${totp_verified}"
[[ -n "$totp_key" ]] && set_ini Account totp_key "${totp_key}"
# Welcome session
[[ -n "$welcome_session" ]] && set_ini Other welcome_session "${welcome_session}"
# If username and password are set but welcome_session isn't, disable it
if [[ -n "$username" && -n "$password" && -z "$welcome_session" ]]; then
set_ini Other welcome_session "false"
fi
# Autostart WireGuard
if [[ -n "$wg_autostart" ]]; then
echo "Configuring WireGuard autostart:"
set_ini WireGuardConfiguration autostart "${wg_autostart}"
fi
# Email (check if any settings need to be configured)
email_vars=("email_server" "email_port" "email_encryption" "email_username" "email_password" "email_from" "email_template")
for var in "${email_vars[@]}"; do
if [ -n "${!var}" ]; then
echo "Configuring email settings:"
break
fi
done
# Email (iterate through all possible fields)
email_fields=("server:email_server" "port:email_port" "encryption:email_encryption"
"username:email_username" "email_password:email_password"
"send_from:email_from" "email_template:email_template")
for field_pair in "${email_fields[@]}"; do
IFS=: read -r field var <<< "$field_pair"
[[ -n "${!var}" ]] && set_ini Email "$field" "${!var}"
done
}
ensure_blocking() {
# Start service and monitor logs
start_and_monitor() {
printf "\n---------------------- STARTING CORE -----------------------\n"
# Due to some instances complaining about this, making sure its there every time.
mkdir -p /dev/net
mknod /dev/net/tun c 10 200
chmod 600 /dev/net/tun
# Actually starting WGDashboard
echo "Starting WGDashboard directly with Gunicorn..."
[[ ! -d ${WGDASH}/src/log ]] && mkdir ${WGDASH}/src/log
[[ ! -d ${WGDASH}/src/download ]] && mkdir ${WGDASH}/src/download
${WGDASH}/src/venv/bin/gunicorn --config ${WGDASH}/src/gunicorn.conf.py
/usr/sbin/resolvconf -u
if [ $? -ne 0 ]; then
echo "Loading WGDashboard failed... Look above for details."
fi
# Wait a second before continuing, to give the python program some time to get ready.
echo -e "\nEnsuring container continuation."
local max_rounds="10"
local round="0"
# Hang in there for 10s for Gunicorn to get ready
while true; do
round=$((round + 1))
local latest_error=$(ls -t ${WGDASH}/src/log/error_*.log 2> /dev/null | head -n 1)
if [[ $round -eq $max_rounds ]]; then
echo "Reached breaking point!"
break
fi
if [[ -z $latest_error ]]; then
echo -e "Logs not yet present! Retrying in 1 second!"
sleep 1s
echo "Ensuring container continuation."
# This function checks if the latest error log is created and tails it for docker logs uses.
if find "/opt/wireguarddashboard/app/src/log" -mindepth 1 -maxdepth 1 -type f | read -r; then
latestErrLog=$(find /opt/wireguarddashboard/app/src/log -name "error_*.log" | head -n 1)
latestAccLog=$(find /opt/wireguarddashboard/app/src/log -name "access_*.log" | head -n 1)
tail -f "${latestErrLog}" "${latestAccLog}"
else
break
fi
# Blocking command in case of erroring. So the container does not quit.
sleep infinity
done
if [[ -z $latest_error ]]; then
echo -e "No error logs founds... Please investigate.\nExiting in 3 minutes..."
sleep 180s
exit 1
else
tail -f "$latest_error" &
tail_pid=$!
wait $tail_pid
fi
}
# Execute functions for the WireGuard Dashboard services, then set the environment variables
clean_up
start_core
# Main execution flow
ensure_installation
set_envvars
ensure_blocking
start_and_monitor

8
docker/wg0.conf.template Normal file
View File

@ -0,0 +1,8 @@
[Interface]
Address = ${wg_net}/24
PrivateKey =
PostUp = iptables -t nat -I POSTROUTING 1 -s ${wg_net}/24 -o ${out_adapt} -j MASQUERADE; iptables -I FORWARD -i wg0 -o wg0 -j DROP
PreDown = iptables -t nat -D POSTROUTING -s ${wg_net}/24 -o ${out_adapt} -j MASQUERADE; iptables -D FORWARD -i wg0 -o wg0 -j DROP
ListenPort = ${wg_port}
SaveConfig = true
DNS = ${global_dns}

View File

@ -1,612 +0,0 @@
# 📖 API Document for WGDashboard
**Version: v4.0**
**Created by: Donald Zou**
<!-- TOC -->
* [📖 API Document for WGDashboard](#-api-document-for-wgdashboard)
* [🔑 How to use API Key?](#-how-to-use-api-key)
* [Create API Key](#create-api-key)
* [Use API Key](#use-api-key)
* [API Endpoints](#api-endpoints)
* [Handshake to Server](#handshake-to-server)
* [Request](#request)
* [Response](#response)
* [Validate Authentication](#validate-authentication)
* [Request](#request-1)
* [Response](#response-1)
* [Authenticate](#authenticate)
* [Request](#request-2)
* [Body Parameters](#body-parameters)
* [Response](#response-2)
* [Sign Out](#sign-out)
* [Request](#request-3)
* [Response](#response-3)
* [Get WireGuard Configurations](#get-wireguard-configurations)
* [Request](#request-4)
* [Response](#response-4)
* [Add WireGuard Configuration](#add-wireguard-configuration)
* [Request](#request-5)
* [Body Parameters](#body-parameters-1)
* [Response](#response-5)
* [Toggle WireGuard Configuration](#toggle-wireguard-configuration)
* [Request](#request-6)
* [Query String Parameter](#query-string-parameter)
* [Response](#response-6)
* [Get WGDashboard Configuration](#get-wgdashboard-configuration)
* [Request](#request-7)
* [Response](#response-7)
* [Update WGDashboard Configuration Item](#update-wgdashboard-configuration-item)
* [Request](#request-8)
* [Body Parameters](#body-parameters-2)
* [Response](#response-8)
* [Get WGDashboard API Keys](#get-wgdashboard-api-keys)
* [Request](#request-9)
* [Response](#response-9)
* [Add WGDashboard API Key](#add-wgdashboard-api-key)
* [Request](#request-10)
* [Body Parameters](#body-parameters-3)
* [Response](#response-10)
* [Endpoint](#endpoint)
* [Request](#request-11)
* [Response](#response-11)
<!-- TOC -->
<hr>
## 🔑 How to use API Key?
### Create API Key
1. To request an API Key, simply login to your WGDashboard, go to **Settings**, scroll to the very bottom. Click the **switch** on the right to enable API Key.
2. Click the blur **Create** button, set an **expiry date** you want or **never expire**, then click **Done**.
### Use API Key
- Simply add `wg-dashboard-apikey` with the value of your API key into the HTTP Header.
```javascript
fetch('http://server:10086/api/handshake', {
headers: {
'content-type': 'application/json',
'wg-dashboard-apikey': 'insert your api key here'
},
method: "GET"
})
```
## API Endpoints
### Handshake to Server
This endpoint is designed for a simple handshake when using API key to connect. If `status` is `true` that means
#### Request
`GET /api/handshake`
#### Response
`200 - OK`
```json
{
"data": null,
"message": null,
"status": true
}
```
`401 - UNAUTHORIZED`
```json
{
"data": null,
"message": "Unauthorized access.",
"status": false
}
```
> Notice: this `401` response will return at all endpoint if your API Key or session is invalid.
<hr>
### Validate Authentication
This endpoint if needed for non-cross-server access. This will check if the cookie on the client side is still valid on the server side.
#### Request
`GET /api/validateAuthentication`
#### Response
`200 - OK`
Session is still valid
```json
{
"data": null,
"message": null,
"status": true
}
```
Session is invalid
```json
{
"data": null,
"message": "Invalid authentication.",
"status": false
}
```
<hr>
### Authenticate
This endpoint is dedicated for non-cross-server access. It is used to authenticate user's username, password and TOTP
#### Request
`POST /api/authenticate`
##### Body Parameters
```json
{
"username": "admin",
"password": "admin",
"totp": "123456"
}
```
| Parameter | Type |
|------------|--------|
| `username` | string |
| `password` | string |
| `totp` | string |
#### Response
`200 - OK`
If username, password and TOTP matched
```json
{
"data": null,
"message": null,
"status": true
}
```
If username, password or TOTP is not match
```json
{
"data": null,
"message": "Sorry, your username, password or OTP is incorrect.",
"status": false
}
```
<hr>
### Sign Out
To remove the current session on server side
#### Request
`GET /api/signout`
#### Response
`200 - OK`
```json
{
"data": null,
"message": null,
"status": true
}
```
<hr>
### Get WireGuard Configurations
To get all WireGuard configurations in `/etc/wireguard`
#### Request
`GET /api/getWireguardConfigurations`
#### Response
`200 - OK`
```json
{
"data": [
{
"Address": "10.200.200.1/24",
"ConnectedPeers": 0,
"DataUsage": {
"Receive": 0.1582,
"Sent": 2.1012999999999997,
"Total": 2.2595
},
"ListenPort": "51820",
"Name": "wg0",
"PostDown": "iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s1 -j MASQUERADE;",
"PostUp": "iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s1 -j MASQUERADE;",
"PreDown": "",
"PreUp": "",
"PrivateKey": "8DsSMli3okgUx5frKbFQ0fMW5ZMyqyxOdOW7+g21L18=",
"PublicKey": "GQlGi8QJ93hWY7L2xlJyh+7S6+ekER9xP11T92T0O0Q=",
"SaveConfig": true,
"Status": false
}
],
"message": null,
"status": true
}
```
<hr>
### Add WireGuard Configuration
Add a new WireGuard Configuration
#### Request
`POST /api/addWireguardConfiguration`
##### Body Parameters
```json
{
"ConfigurationName": "wg0",
"Address": "10.0.0.1/24",
"ListenPort": 51820,
"PrivateKey": "eJuuamCgakVs2xUZGHh/g7C6Oy89JGh7eE2jjEGbbFc=",
"PublicKey": "3Ruirgw9qNRwNpBepkiVjjSe82tY+lDZr6WaFC4wO2g=",
"PresharedKey": "GMMLKWdJlgsKVoR26BJPsNbDXyfILL+x1Nd6Ecmn4lg=",
"PreUp": "",
"PreDown": "iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s1 -j MASQUERADE;",
"PostUp": "iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s1 -j MASQUERADE;",
"PostDown": ""
}
```
| Parameter | Type |
|---------------------|--------|
| `ConfigurationName` | string |
| `Address` | string |
| `ListenPort` | int |
| `PrivateKey` | string |
| `PublicKey` | string |
| `PresharedKey` | string |
| `PreUp` | string |
| `PreDown` | string |
| `PostUp` | string |
| `PostDown` | string |
#### Response
`200 - OK`
If everything is good
```json
{
"data": null,
"message": null,
"status": true
}
```
If the new configuration's `ConfigurationName` is already existed
```json
{
"data": null,
"message": "Already have a configuration with the name \"wg0\"",
"status": false
}
```
If the new configuration's `ListenPort` is used by another configuration
```json
{
"data": null,
"message": "Already have a configuration with the port \"51820\"",
"status": false
}
```
If the new configuration's `Address` is used by another configuration
```json
{
"data": null,
"message": "Already have a configuration with the address \"10.0.0.1/24\"",
"status": false
}
```
<hr>
### Toggle WireGuard Configuration
To turn on/off of a WireGuard Configuration
#### Request
`GET /api/toggleWireguardConfiguration/?configurationName=`
##### Query String Parameter
| Parameter | Type |
|---------------------|--------|
| `configurationName` | string |
#### Response
`200 - OK`
If toggle is successful, server will return the current status in `status`: `true` or `false` indicating if the configuration is up or not.
```json
{
"data": true,
"message": null,
"status": true
}
```
If the `configurationName` provided does not exist
```json
{
"data": null,
"message": "Please provide a valid configuration name",
"status": false
}
```
<hr>
### Get WGDashboard Configuration
Get the WGDashboard Configuration, such as `dashboard_theme`...
#### Request
`GET /api/getDashboardConfiguration`
#### Response
`200 - OK`
```json
{
"data": {
"Account": {
"enable_totp": false,
"password": "some hashed value :(",
"totp_verified": false,
"username": "admin"
},
"Database": {
"type": "sqlite"
},
"Other": {
"welcome_session": false
},
"Peers": {
"peer_display_mode": "grid",
"peer_endpoint_allowed_ip": "0.0.0.0/0",
"peer_global_dns": "1.1.1.1",
"peer_keep_alive": "21",
"peer_mtu": "1420",
"remote_endpoint": "192.168.2.38"
},
"Server": {
"app_ip": "0.0.0.0",
"app_port": "10086",
"app_prefix": "",
"auth_req": true,
"dashboard_api_key": true,
"dashboard_refresh_interval": "5000",
"dashboard_sort": "status",
"dashboard_theme": "dark",
"version": "v4.0",
"wg_conf_path": "/etc/wireguard"
}
},
"message": null,
"status": true
}
```
<hr>
### Update WGDashboard Configuration Item
Update the WGDashboard Configuration one at a time
#### Request
`POST /api/updateDashboardConfigurationItem`
##### Body Parameters
```json
{
"section": "Server",
"key": "dashboard_theme",
"value": "dark"
}
```
| Parameter | Type | |
|-----------|--------|----------------------------------------------------------|
| `section` | string | Each section in the `wg-dashboard.ini` |
| `key` | string | Each key/value pair under each in the `wg-dashboard.ini` |
| `value` | string | Value for this key/value pair |
#### Response
`200 - OK`
If update is success
```json
{
"data": true,
"message": null,
"status": true
}
```
If update failed
```json
{
"data": true,
"message": "Message related to the error will appear here",
"status": false
}
```
<hr>
### Get WGDashboard API Keys
Get a list of active API key in WGDashboard
#### Request
`GET /api/getDashboardAPIKeys`
#### Response
`200 - OK`
If API Key function is enabled and there are active API keys
> If `ExpiredAt` is `null`, that means this API key will never expire
```json
{
"data": [
{
"CreatedAt": "2024-08-15 00:42:31",
"ExpiredAt": null,
"Key": "AXt1x3TZMukmA-eSnAyESy08I14n20boppSsknHOB-Y"
},
{
"CreatedAt": "2024-08-14 22:50:44",
"ExpiredAt": "2024-08-21 22:50:43",
"Key": "ry0Suo0BrypSMzbq0C_TjkEcgrFHHj6UBZGmC2-KI2o"
}
],
"message": null,
"status": true
}
```
If API key function is disabled
```json
{
"data": null,
"message": "Dashboard API Keys function is disabled",
"status": false
}
```
<hr>
### Add WGDashboard API Key
Add a new API Key in WGDashboard
#### Request
`POST /api/newDashboardAPIKey`
##### Body Parameters
```json
{
"neverExpire": false,
"ExpiredAt": "2024-12-31 16:00:00"
}
```
| Parameter | Type | |
|---------------|--------|-----------------------------------------------------------------------------------|
| `neverExpire` | bool | If this is `false`, please specify a date in `ExpiredAt` |
| `ExpiredAt` | string | If `neverExpire` is `true`, this can be omitted. Format is `YYYY-MM-DD hh:mm:ss`. |
#### Response
`200 - OK`
If success, it will return the latest list of API Keys
```json
{
"data": [
{
"CreatedAt": "2024-08-15 00:42:31",
"ExpiredAt": null,
"Key": "AXt1x3TZMukmA-eSnAyESy08I14n20boppSsknHOB-Y"
},
{
"CreatedAt": "2024-08-14 22:50:44",
"ExpiredAt": "2024-12-31 16:50:43",
"Key": "ry0Suo0BrypSMzbq0C_TjkEcgrFHHj6UBZGmC2-KI2o"
}
],
"message": null,
"status": true
}
```
If API key function is disabled
```json
{
"data": null,
"message": "Dashboard API Keys function is disabled",
"status": false
}
```
<hr>
### Endpoint
Description
#### Request
`GET`
#### Response
`200 - OK`
```json
{
"data": true,
"message": null,
"status": true
}
```

View File

@ -1,105 +0,0 @@
# ⏰ Changelogs of WGDashboard
#### v3.0.0 - v3.0.6.2 - Jan 18, 2022
- 🎉 **New Features**
- **Moved from TinyDB to SQLite**: SQLite provide a better performance and loading speed when getting peers! Also avoided crashing the database due to **race condition**.
- **Added Gunicorn WSGI Server**: This could provide more stable on handling HTTP request, and more flexibility in the future (such as HTTPS support). **BIG THANKS to @pgalonza :heart:**
- **Add Peers by Bulk:** User can add peers by bulk, just simply set the amount and click add.
- **Delete Peers by Bulk**: User can delete peers by bulk, without deleting peers one by one.
- **Download Peers in Zip**: User can download all *downloadable* peers in a zip.
- **Added Pre-shared Key to peers:** Now each peer can add with a pre-shared key to enhance security. Previously added peers can add the pre-shared key through the peer setting button.
- **Redirect Back to Previous Page:** The dashboard will now redirect you back to your previous page if the current session got timed out and you need to sign in again.
- **Added Some [🥘 Experimental Functions](#-experimental-functions)**
- 🪚 **Bug Fixed**
- [IP Sorting range issues #99](https://github.com/donaldzou/WGDashboard/issues/99) [❤️ @barryboom]
- [INvalid character written to tunnel json file #108](https://github.com/donaldzou/WGDashboard/issues/108) [❤️ @ikidd]
- [Add IPv6 #91](https://github.com/donaldzou/WGDashboard/pull/91) [❤️ @pgalonza]
- [Added MTU and PersistentKeepalive to QR code and download files #112](https://github.com/donaldzou/WGDashboard/pull/112) [:heart: @reafian]
- **And many other bugs provided by our beloved users** :heart:
- **🧐 Other Changes**
- **Key generating moved to front-end**: No longer need to use the server's WireGuard to generate keys, thanks to the `wireguard.js` from the [official repository](https://git.zx2c4.com/wireguard-tools/tree/contrib/keygen-html/wireguard.js)!
- **Peer transfer calculation**: each peer will now show all transfer amount (previously was only showing transfer amount from the last configuration start-up).
- **UI adjustment on running peers**: peers will have a new style indicating that it is running.
- **`wgd.sh` finally can update itself**: So now user could update the whole dashboard from `wgd.sh`, with the `update` command.
- **Minified JS and CSS files**: Although only a small changes on the file size, but I think is still a good practice to save a bit of bandwidth ;)
*And many other small changes for performance and bug fixes! :laughing:*
#### v2.3.1 - Sep 8, 2021
- Updated dashboard's name to **WGDashboard**!!
#### v2.3 - Sep 8, 2021
- 🎉 **New Features**
- **Update directly from `wgd.sh`:** Now you can update WGDashboard directly from the bash script.
- **Displaying Peers:** You can switch the display mode between list and table in the configuration page.
- 🪚 **Bug Fixed**
- [Peer DNS Validation Fails #67](issues/67): Added DNS format check. [❤️ @realfian]
- [configparser.NoSectionError: No section: 'Interface' #66](issues/66): Changed permission requirement for `etc/wireguard` from `744` to `755`. [❤️ @ramalmaty]
- [Feature request: Interface not loading when information missing #73](issues/73): Fixed when Configuration Address and Listen Port is missing will crash the dashboard. [❤️ @js32]
- [Remote Peer, MTU and PersistentKeepalives added #70](pull/70): Added MTU, remote peer and Persistent Keepalive. [❤️ @realfian]
- [Fixes DNS check to support search domain #65](pull/65): Added allow input domain into DNS. [❤️@davejlong]
- **🧐 Other Changes**
- Moved Add Peer Button into the right bottom corner.
#### v2.2.1 - Aug 16, 2021
Bug Fixed:
- Added support for full subnet on Allowed IP
- Peer setting Save button
#### v2.2 - Aug 14, 2021
- 🎉 **New Features**
- **Add new peers**: Now you can add peers directly on dashboard, it will generate a pair of private key and public key. You can also set its DNS, endpoint allowed IPs. Both can set a default value in the setting page. [❤️ in [#44](https://github.com/donaldzou/wireguard-dashboard/issues/44)]
- **QR Code:** You can add the private key in peer setting of your existed peer to create a QR code. Or just create a new one, dashboard will now be able to auto generate a private key and public key ;) Don't worry, all keys will be generated on your machine, and **will delete all key files after they got generated**. [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
- **Peer configuration file download:** Same as QR code, you now can download the peer configuration file, so you don't need to manually input all the details on the peer machine! [❤️ in [#40](https://github.com/donaldzou/wireguard-dashboard/issues/40)]
- **Search peers**: You can now search peers by their name.
- **Autostart on boot:** Added a tutorial on how to start the dashboard to on boot! Please read the [tutorial below](#autostart-wireguard-dashboard-on-boot). [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
- **Click to copy**: You can now click and copy all peer's public key and configuration's public key.
- ....
- 🪚 **Bug Fixed**
- When there are comments in the wireguard config file, will cause the dashboard to crash.
- Used regex to search for config files.
- **🧐 Other Changes**
- Moved all external CSS and JavaScript file to local hosting (Except Bootstrap Icon, due to large amount of SVG files).
- Updated Python dependencies
- Flask: `v1.1.2 => v2.0.1`
- Jinja: `v2.10.1 => v3.0.1`
- icmplib: `v2.1.1 => v3.0.1`
- Updated CSS/JS dependencies
- Bootstrap: `v4.5.3 => v4.6.0`
- UI adjustment
- Adjusted how peers will display in larger screens, used to be 1 row per peer, now is 3 peers in 1 row.
#### v2.1 - Jul 2, 2021
- Added **Ping** and **Traceroute** tools!
- Adjusted the calculation of data usage on each peers
- Added refresh interval of the dashboard
- Bug fixed when no configuration on fresh install ([#23](https://github.com/donaldzou/wireguard-dashboard/issues/23))
- Fixed crash when too many peers ([#22](https://github.com/donaldzou/wireguard-dashboard/issues/22))
#### v2.0 - May 5, 2021
- Added login function to dashboard
- ***I'm not using the most ideal way to store the username and password, feel free to provide a better way to do this if you any good idea!***
- Added a config file to the dashboard
- Dashboard config can be change within the **Setting** tab on the side bar
- Adjusted UI
- And much more!
#### v1.1.2 - Apr 3, 2021
- Resolved issue [#3](https://github.com/donaldzou/wireguard-dashboard/issues/3).
#### v1.1.1 - Apr 2, 2021
- Able to add a friendly name to each peer. Thanks [#2](https://github.com/donaldzou/wireguard-dashboard/issues/2) !
#### v1.0 - Dec 27, 2020
- Added the function to remove peers

17
package-lock.json generated
View File

@ -1,17 +0,0 @@
{
"name": "Wireguard-Dashboard",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"dayjs": "^1.11.12"
}
},
"node_modules/dayjs": {
"version": "1.11.12",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
"integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg=="
}
}
}

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"dayjs": "^1.11.12"
}
}

View File

@ -1,244 +0,0 @@
import ipaddress, subprocess, datetime, os, util
from datetime import datetime, timedelta
from flask import jsonify
from util import *
import configparser
notEnoughParameter = {"status": False, "reason": "Please provide all required parameters."}
good = {"status": True, "reason": ""}
def ret(status=True, reason="", data=""):
return {"status": status, "reason": reason, "data": data}
def togglePeerAccess(data, g):
checkUnlock = g.cur.execute(f"SELECT * FROM {data['config']} WHERE id='{data['peerID']}'").fetchone()
if checkUnlock:
moveUnlockToLock = g.cur.execute(
f"INSERT INTO {data['config']}_restrict_access SELECT * FROM {data['config']} WHERE id = '{data['peerID']}'")
if g.cur.rowcount == 1:
print(g.cur.rowcount)
print(util.deletePeers(data['config'], [data['peerID']], g.cur, g.db))
else:
moveLockToUnlock = g.cur.execute(
f"SELECT * FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'").fetchone()
try:
if len(moveLockToUnlock[-1]) == 0:
status = subprocess.check_output(
f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]}",
shell=True, stderr=subprocess.STDOUT)
else:
now = str(datetime.datetime.now().strftime("%m%d%Y%H%M%S"))
f_name = now + "_tmp_psk.txt"
f = open(f_name, "w+")
f.write(moveLockToUnlock[-1])
f.close()
subprocess.check_output(
f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]} preshared-key {f_name}",
shell=True, stderr=subprocess.STDOUT)
os.remove(f_name)
status = subprocess.check_output(f"wg-quick save {data['config']}", shell=True, stderr=subprocess.STDOUT)
g.cur.execute(
f"INSERT INTO {data['config']} SELECT * FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'")
if g.cur.rowcount == 1:
g.cur.execute(f"DELETE FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'")
except subprocess.CalledProcessError as exc:
return {"status": False, "reason": str(exc.output.strip())}
return good
class managePeer:
def getPeerDataUsage(self, data, cur):
now = datetime.now()
now_string = now.strftime("%d/%m/%Y %H:%M:%S")
interval = {
"30min": now - timedelta(hours=0, minutes=30),
"1h": now - timedelta(hours=1, minutes=0),
"6h": now - timedelta(hours=6, minutes=0),
"24h": now - timedelta(hours=24, minutes=0),
"all": ""
}
if data['interval'] not in interval.keys():
return {"status": False, "reason": "Invalid interval."}
intv = ""
if data['interval'] != "all":
t = interval[data['interval']].strftime("%d/%m/%Y %H:%M:%S")
intv = f" AND time >= '{t}'"
timeData = cur.execute(f"SELECT total_receive, total_sent, time FROM wg0_transfer WHERE id='{data['peerID']}' {intv} ORDER BY time DESC;")
chartData = []
for i in timeData:
chartData.append({
"total_receive": i[0],
"total_sent": i[1],
"time": i[2]
})
return {"status": True, "reason": "", "data": chartData}
class manageConfiguration:
def AddressCheck(self, data):
address = data['address']
address = address.replace(" ", "")
address = address.split(',')
amount = 0
for i in address:
try:
ips = ipaddress.ip_network(i, False)
amount += ips.num_addresses
except ValueError as e:
return {"status": False, "reason": str(e)}
if amount >= 1:
return {"status": True, "reason": "", "data": f"Total of {amount} IPs"}
else:
return {"status": True, "reason": "", "data": f"0 available IPs"}
def PortCheck(self, data, configs):
port = data['port']
if (not port.isdigit()) or int(port) < 1 or int(port) > 65535:
return {"status": False, "reason": f"Invalid port."}
for i in configs:
if i['port'] == port:
return {"status": False, "reason": f"{port} used by {i['conf']}."}
checkSystem = subprocess.run(f'ss -tulpn | grep :{port} > /dev/null', shell=True)
if checkSystem.returncode != 1:
return {"status": False, "reason": f"Port {port} used by other process in your system."}
return good
def NameCheck(self, data, configs):
name = data['name']
name = name.replace(" ", "")
for i in configs:
if name == i['conf']:
return {"status": False, "reason": f"{name} already existed."}
illegal_filename = ["(Space)", " ", ".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2",
"com3",
"com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"]
for i in illegal_filename:
name = name.replace(i, "")
if len(name) == 0:
return {"status": False, "reason": "Invalid name."}
return good
def addConfiguration(self, data, configs, WG_CONF_PATH):
output = ["[Interface]", "SaveConfig = true"]
required = ['addConfigurationPrivateKey', 'addConfigurationListenPort',
'addConfigurationAddress', 'addConfigurationPreUp', 'addConfigurationPreDown',
'addConfigurationPostUp', 'addConfigurationPostDown']
for i in required:
e = data[i]
if len(e) != 0:
key = i.replace("addConfiguration", "")
o = f"{key} = {e}"
output.append(o)
name = data['addConfigurationName']
illegal_filename = ["(Space)", " ", ".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2",
"com3",
"com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"]
for i in illegal_filename:
name = name.replace(i, "")
try:
newFile = open(f"{WG_CONF_PATH}/{name}.conf", "w+")
newFile.write("\n".join(output))
except Exception as e:
return {"status": False, "reason": str(e)}
return {"status": True, "reason": "", "data": name}
def deleteConfiguration(self, data, config, g, WG_CONF_PATH):
confs = []
for i in config:
confs.append(i['conf'])
print(confs)
if data['name'] not in confs:
return {"status": False, "reason": "Configuration does not exist", "data": ""}
for i in config:
if i['conf'] == data['name']:
if i['status'] == "running":
try:
subprocess.check_output("wg-quick down " + data['name'], shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc:
return {"status": False, "reason": "Can't stop peer", "data": str(exc.output.strip().decode("utf-8"))}
g.cur.execute(f'DROP TABLE {data["name"]}')
g.cur.execute(f'DROP TABLE {data["name"]}_restrict_access')
g.db.commit()
try:
os.remove(f'{WG_CONF_PATH}/{data["name"]}.conf')
except Exception as e:
return {"status": False, "reason": "Can't delete peer", "data": str(e)}
return good
def getConfigurationInfo(self, configName, WG_CONF_PATH):
conf = configparser.RawConfigParser(strict=False)
conf.optionxform = str
try:
with open(f'{WG_CONF_PATH}/{configName}.conf', 'r'):
conf.read(f'{WG_CONF_PATH}/{configName}.conf')
if not conf.has_section("Interface"):
return ret(status=False, reason="No [Interface] in configuration file")
return ret(data=dict(conf['Interface']))
except FileNotFoundError as err:
return ret(status=False, reason=str(err))
def saveConfiguration(self, data, WG_CONF_PATH, configs):
conf = configparser.RawConfigParser(strict=False)
conf.optionxform = str
configName = data['configurationName']
pc = manageConfiguration.PortCheck(self, {'port': data['ListenPort']}, configs)
if pc['status']:
try:
newData = []
with open(f'{WG_CONF_PATH}/{configName}.conf', 'r') as f:
conf.read(f'{WG_CONF_PATH}/{configName}.conf')
if not conf.has_section("Interface"):
return ret(status=False, reason="No [Interface] in configuration file")
l = ['ListenPort', 'PostUp', 'PostDown', 'PreUp', 'PreDown']
for i in l:
conf.set("Interface", i, data[i])
conf.remove_section("Peer")
newData = list(map(lambda x : f"{x[0]} = {x[1]}\n", list(conf.items("Interface"))))
originalData = f.readlines()
for i in range(len(originalData)):
if originalData[i] == "[Peer]\n":
originalData = originalData[i:]
break
newData.insert(0, "[Interface]\n")
newData.append("\n")
newData = newData + originalData
conf.clear()
try:
check = subprocess.check_output("wg-quick down " + configName,
shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc:
pass
with open(f'{WG_CONF_PATH}/{configName}.conf', 'w') as f:
for i in newData:
f.write(i)
try:
check = subprocess.check_output("wg-quick up " + configName,
shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc:
pass
return ret()
except FileNotFoundError as err:
return ret(status=False, reason=str(err))
else:
return pc
class settings:
def setTheme(self, theme, config, setConfig):
themes = ['light', 'dark']
if theme not in themes:
return ret(status=False, reason="Theme does not exist")
config['Server']['dashboard_theme'] = theme
setConfig(config)
return ret()

View File

@ -1,7 +0,0 @@
for ((i = 0 ; i<$1 ; i++ ))
do
privateKey=$(wg genkey)
presharedKey=$(wg genkey)
publicKey=$(wg pubkey <<< "$privateKey")
echo "$privateKey,$publicKey,$presharedKey"
done

232
src/client.py Normal file
View File

@ -0,0 +1,232 @@
import datetime
from tzlocal import get_localzone
from functools import wraps
from flask import Blueprint, render_template, abort, request, Flask, current_app, session, redirect, url_for
import os
from modules.WireguardConfiguration import WireguardConfiguration
from modules.DashboardConfig import DashboardConfig
from modules.Email import EmailSender
def ResponseObject(status=True, message=None, data=None, status_code = 200) -> Flask.response_class:
response = Flask.make_response(current_app, {
"status": status,
"message": message,
"data": data
})
response.status_code = status_code
response.content_type = "application/json"
return response
from modules.DashboardClients import DashboardClients
def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration], dashboardConfig: DashboardConfig, dashboardClients: DashboardClients):
client = Blueprint('client', __name__, template_folder=os.path.abspath("./static/dist/WGDashboardClient"))
prefix = f'{dashboardConfig.GetConfig("Server", "app_prefix")[1]}/client'
def login_required(f):
@wraps(f)
def func(*args, **kwargs):
if session.get("Email") is None or session.get("TotpVerified") is None or not session.get("TotpVerified") or session.get("Role") != "client":
return ResponseObject(False, "Unauthorized access.", data=None, status_code=401)
if not dashboardClients.GetClient(session.get("ClientID")):
session.clear()
return ResponseObject(False, "Unauthorized access.", data=None, status_code=401)
return f(*args, **kwargs)
return func
@client.before_request
def clientBeforeRequest():
if not dashboardConfig.GetConfig("Clients", "enable")[1]:
abort(404)
if request.method.lower() == 'options':
return ResponseObject(True)
@client.post(f'{prefix}/api/signup')
def ClientAPI_SignUp():
data = request.get_json()
status, msg = dashboardClients.SignUp(**data)
return ResponseObject(status, msg)
@client.get(f'{prefix}/api/signin/oidc/providers')
def ClientAPI_SignIn_OIDC_GetProviders():
_, oidc = dashboardConfig.GetConfig("OIDC", "client_enable")
if not oidc:
return ResponseObject(status=False, message="OIDC is disabled")
return ResponseObject(data=dashboardClients.OIDC.GetProviders())
@client.post(f'{prefix}/api/signin/oidc')
def ClientAPI_SignIn_OIDC():
_, oidc = dashboardConfig.GetConfig("OIDC", "client_enable")
if not oidc:
return ResponseObject(status=False, message="OIDC is disabled")
data = request.get_json()
status, oidcData = dashboardClients.SignIn_OIDC(**data)
if not status:
return ResponseObject(status, oidcData)
session['Email'] = oidcData.get('email')
session['Role'] = 'client'
session['TotpVerified'] = True
return ResponseObject()
@client.post(f'{prefix}/api/signin')
def ClientAPI_SignIn():
data = request.get_json()
status, msg = dashboardClients.SignIn(**data)
if status:
session['Email'] = data.get('Email')
session['Role'] = 'client'
session['TotpVerified'] = False
return ResponseObject(status, msg)
@client.post(f'{prefix}/api/resetPassword/generateResetToken')
def ClientAPI_ResetPassword_GenerateResetToken():
date = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
emailSender = EmailSender(dashboardConfig)
if not emailSender.ready():
return ResponseObject(False, "We can't send you an email due to your Administrator has not setup email service. Please contact your administrator.")
data = request.get_json()
email = data.get('Email', None)
if not email:
return ResponseObject(False, "Please provide a valid Email")
u = dashboardClients.SignIn_UserExistence(email)
if not u:
return ResponseObject(False, "Please provide a valid Email")
token = dashboardClients.GenerateClientPasswordResetToken(u.get('ClientID'))
status, msg = emailSender.send(
email, "[WGDashboard | Client] Reset Password",
f"Hi {email}, \n\nIt looks like you're trying to reset your password at {date} \n\nEnter this 6 digits code on the Forgot Password to continue:\n\n{token}\n\nThis code will expire in 30 minutes for your security. If you didnt request a password reset, you can safely ignore this email—your current password will remain unchanged.\n\nIf you need help, feel free to contact support.\n\nBest regards,\n\nWGDashboard"
)
return ResponseObject(status, msg)
@client.post(f'{prefix}/api/resetPassword/validateResetToken')
def ClientAPI_ResetPassword_ValidateResetToken():
data = request.get_json()
email = data.get('Email', None)
token = data.get('Token', None)
if not all([email, token]):
return ResponseObject(False, "Please provide a valid Email")
u = dashboardClients.SignIn_UserExistence(email)
if not u:
return ResponseObject(False, "Please provide a valid Email")
return ResponseObject(status=dashboardClients.ValidateClientPasswordResetToken(u.get('ClientID'), token))
@client.post(f'{prefix}/api/resetPassword')
def ClientAPI_ResetPassword():
data = request.get_json()
email = data.get('Email', None)
token = data.get('Token', None)
password = data.get('Password', None)
confirmPassword = data.get('ConfirmPassword', None)
if not all([email, token, password, confirmPassword]):
return ResponseObject(False, "Please provide a valid Email")
u = dashboardClients.SignIn_UserExistence(email)
if not u:
return ResponseObject(False, "Please provide a valid Email")
if not dashboardClients.ValidateClientPasswordResetToken(u.get('ClientID'), token):
return ResponseObject(False, "Verification code is either invalid or expired")
status, msg = dashboardClients.ResetClientPassword(u.get('ClientID'), password, confirmPassword)
dashboardClients.RevokeClientPasswordResetToken(u.get('ClientID'), token)
return ResponseObject(status, msg)
@client.get(f'{prefix}/api/signout')
def ClientAPI_SignOut():
if session.get("SignInMethod") == "OIDC":
dashboardClients.SignOut_OIDC()
session.clear()
return ResponseObject(True)
@client.get(f'{prefix}/api/signin/totp')
def ClientAPI_SignIn_TOTP():
token = request.args.get('Token', None)
if not token:
return ResponseObject(False, "Please provide TOTP token")
status, msg = dashboardClients.SignIn_GetTotp(token)
return ResponseObject(status, msg)
@client.post(f'{prefix}/api/signin/totp')
def ClientAPI_SignIn_ValidateTOTP():
data = request.get_json()
token = data.get('Token', None)
userProvidedTotp = data.get('UserProvidedTOTP', None)
if not all([token, userProvidedTotp]):
return ResponseObject(False, "Please fill in all fields")
status, msg = dashboardClients.SignIn_GetTotp(token, userProvidedTotp)
if status:
if session.get('Email') is None:
return ResponseObject(False, "Sign in status is invalid", status_code=401)
session['TotpVerified'] = True
profile = dashboardClients.GetClientProfile(session.get("ClientID"))
return ResponseObject(True, data={
"Email": session.get('Email'),
"Profile": profile
})
return ResponseObject(status, msg)
@client.get(prefix)
def ClientIndex():
return render_template('client.html')
@client.get(f'{prefix}/api/serverInformation')
def ClientAPI_ServerInformation():
return ResponseObject(data={
"ServerTimezone": str(get_localzone())
})
@client.get(f'{prefix}/api/validateAuthentication')
@login_required
def ClientAPI_ValidateAuthentication():
return ResponseObject(True)
@client.get(f'{prefix}/api/configurations')
@login_required
def ClientAPI_Configurations():
return ResponseObject(True, data=dashboardClients.GetClientAssignedPeers(session['ClientID']))
@client.get(f'{prefix}/api/settings/getClientProfile')
@login_required
def ClientAPI_Settings_GetClientProfile():
return ResponseObject(data={
"Email": session.get("Email"),
"SignInMethod": session.get("SignInMethod"),
"Profile": dashboardClients.GetClientProfile(session.get("ClientID"))
})
@client.post(f'{prefix}/api/settings/updatePassword')
@login_required
def ClientAPI_Settings_UpdatePassword():
data = request.get_json()
status, message = dashboardClients.UpdateClientPassword(session['ClientID'], **data)
return ResponseObject(status, message)
return client

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,26 @@
import dashboard
from datetime import datetime
global sqldb, cursor, DashboardConfig, WireguardConfigurations, AllPeerJobs, JobLogger
global sqldb, cursor, DashboardConfig, WireguardConfigurations, AllPeerJobs, JobLogger, Dash
app_host, app_port = dashboard.gunicornConfig()
date = datetime.today().strftime('%Y_%m_%d_%H_%M_%S')
def post_worker_init(worker):
dashboard.startThreads()
dashboard.DashboardPlugins.startThreads()
worker_class = 'gthread'
workers = 1
threads = 1
threads = 2
bind = f"{app_host}:{app_port}"
daemon = True
pidfile = './gunicorn.pid'
wsgi_app = "dashboard:app"
accesslog = f"./log/access_{date}.log"
log_level = "debug"
loglevel = "info"
capture_output = True
errorlog = f"./log/error_{date}.log"
print(f"[WGDashboard] WGDashboard w/ Gunicorn will be running on {bind}", flush=True)
print(f"[WGDashboard] Access log file is at {accesslog}", flush=True)
print(f"[WGDashboard] Error log file is at {errorlog}", flush=True)
pythonpath = "., ./modules"
print(f"[Gunicorn] WGDashboard w/ Gunicorn will be running on {bind}", flush=True)
print(f"[Gunicorn] Access log file is at {accesslog}", flush=True)
print(f"[Gunicorn] Error log file is at {errorlog}", flush=True)

View File

@ -0,0 +1,92 @@
import os
import random
import re
import subprocess
import uuid
from .Peer import Peer
from .Utilities import ValidateIPAddressesWithRange, ValidateDNSAddress, GenerateWireguardPublicKey
class AmneziaWGPeer(Peer):
def __init__(self, tableData, configuration):
self.advanced_security = tableData["advanced_security"]
super().__init__(tableData, configuration)
def updatePeer(self, name: str, private_key: str,
preshared_key: str,
dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int,
keepalive: int, advanced_security: str) -> tuple[bool, str] or tuple[bool, None]:
if not self.configuration.getStatus():
self.configuration.toggleConfiguration()
existingAllowedIps = [item for row in list(
map(lambda x: [q.strip() for q in x.split(',')],
map(lambda y: y.allowed_ip,
list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row]
if allowed_ip in existingAllowedIps:
return False, "Allowed IP already taken by another peer"
if not ValidateIPAddressesWithRange(endpoint_allowed_ip):
return False, f"Endpoint Allowed IPs format is incorrect"
if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses):
return False, f"DNS format is incorrect"
if type(mtu) is str:
mtu = 0
if type(keepalive) is str:
keepalive = 0
if mtu < 0 or mtu > 1460:
return False, "MTU format is not correct"
if keepalive < 0:
return False, "Persistent Keepalive format is not correct"
if advanced_security != "on" and advanced_security != "off":
return False, "Advanced Security can only be on or off"
if len(private_key) > 0:
pubKey = GenerateWireguardPublicKey(private_key)
if not pubKey[0] or pubKey[1] != self.id:
return False, "Private key does not match with the public key"
try:
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
pskExist = len(preshared_key) > 0
if pskExist:
with open(uid, "w+") as f:
f.write(preshared_key)
newAllowedIPs = allowed_ip.replace(" ", "")
updateAllowedIp = subprocess.check_output(
f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}",
shell=True, stderr=subprocess.STDOUT)
if pskExist: os.remove(uid)
if len(updateAllowedIp.decode().strip("\n")) != 0:
return False, "Update peer failed when updating Allowed IPs"
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
shell=True, stderr=subprocess.STDOUT)
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
return False, "Update peer failed when saving the configuration"
with self.configuration.engine.begin() as conn:
conn.execute(
self.configuration.peersTable.update().values({
"name": name,
"private_key": private_key,
"DNS": dns_addresses,
"endpoint_allowed_ip": endpoint_allowed_ip,
"mtu": mtu,
"keepalive": keepalive,
"preshared_key": preshared_key,
"advanced_security": advanced_security
}).where(
self.configuration.peersTable.c.id == self.id
)
)
self.configuration.getPeers()
return True, None
except subprocess.CalledProcessError as exc:
return False, exc.output.decode("UTF-8").strip()

View File

@ -0,0 +1,322 @@
"""
AmneziaWG Configuration
"""
import random, sqlalchemy, os, subprocess, re, uuid
from flask import current_app
from .PeerJobs import PeerJobs
from .AmneziaWGPeer import AmneziaWGPeer
from .PeerShareLinks import PeerShareLinks
from .Utilities import RegexMatch
from .WireguardConfiguration import WireguardConfiguration
from .DashboardWebHooks import DashboardWebHooks
class AmneziaWireguardConfiguration(WireguardConfiguration):
def __init__(self, DashboardConfig,
AllPeerJobs: PeerJobs,
AllPeerShareLinks: PeerShareLinks,
DashboardWebHooks: DashboardWebHooks,
name: str = None, data: dict = None, backup: dict = None, startup: bool = False):
self.Jc = 0
self.Jmin = 0
self.Jmax = 0
self.S1 = 0
self.S2 = 0
self.H1 = 1
self.H2 = 2
self.H3 = 3
self.H4 = 4
super().__init__(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, name, data, backup, startup, wg=False)
def toJson(self):
self.Status = self.getStatus()
return {
"Status": self.Status,
"Name": self.Name,
"PrivateKey": self.PrivateKey,
"PublicKey": self.PublicKey,
"Address": self.Address,
"ListenPort": self.ListenPort,
"PreUp": self.PreUp,
"PreDown": self.PreDown,
"PostUp": self.PostUp,
"PostDown": self.PostDown,
"SaveConfig": self.SaveConfig,
"Info": self.configurationInfo.model_dump(),
"DataUsage": {
"Total": sum(list(map(lambda x: x.cumu_data + x.total_data, self.Peers))),
"Sent": sum(list(map(lambda x: x.cumu_sent + x.total_sent, self.Peers))),
"Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers)))
},
"ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers))),
"TotalPeers": len(self.Peers),
"Protocol": self.Protocol,
"Table": self.Table,
"Jc": self.Jc,
"Jmin": self.Jmin,
"Jmax": self.Jmax,
"S1": self.S1,
"S2": self.S2,
"H1": self.H1,
"H2": self.H2,
"H3": self.H3,
"H4": self.H4
}
def createDatabase(self, dbName = None):
if dbName is None:
dbName = self.Name
self.peersTable = sqlalchemy.Table(
dbName, self.metadata,
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
sqlalchemy.Column('DNS', sqlalchemy.Text),
sqlalchemy.Column('advanced_security', sqlalchemy.String(255)),
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
sqlalchemy.Column('name', sqlalchemy.Text),
sqlalchemy.Column('total_receive', sqlalchemy.Float),
sqlalchemy.Column('total_sent', sqlalchemy.Float),
sqlalchemy.Column('total_data', sqlalchemy.Float),
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
sqlalchemy.Column('status', sqlalchemy.String(255)),
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
sqlalchemy.Column('mtu', sqlalchemy.Integer),
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
extend_existing=True
)
self.peersRestrictedTable = sqlalchemy.Table(
f'{dbName}_restrict_access', self.metadata,
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
sqlalchemy.Column('DNS', sqlalchemy.Text),
sqlalchemy.Column('advanced_security', sqlalchemy.String(255)),
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
sqlalchemy.Column('name', sqlalchemy.Text),
sqlalchemy.Column('total_receive', sqlalchemy.Float),
sqlalchemy.Column('total_sent', sqlalchemy.Float),
sqlalchemy.Column('total_data', sqlalchemy.Float),
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
sqlalchemy.Column('status', sqlalchemy.String(255)),
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
sqlalchemy.Column('mtu', sqlalchemy.Integer),
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
extend_existing=True
)
self.peersTransferTable = sqlalchemy.Table(
f'{dbName}_transfer', self.metadata,
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
sqlalchemy.Column('total_receive', sqlalchemy.Float),
sqlalchemy.Column('total_sent', sqlalchemy.Float),
sqlalchemy.Column('total_data', sqlalchemy.Float),
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
sqlalchemy.Column('time', (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP),
server_default=sqlalchemy.func.now()),
extend_existing=True
)
self.peersDeletedTable = sqlalchemy.Table(
f'{dbName}_deleted', self.metadata,
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
sqlalchemy.Column('DNS', sqlalchemy.Text),
sqlalchemy.Column('advanced_security', sqlalchemy.String(255)),
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
sqlalchemy.Column('name', sqlalchemy.Text),
sqlalchemy.Column('total_receive', sqlalchemy.Float),
sqlalchemy.Column('total_sent', sqlalchemy.Float),
sqlalchemy.Column('total_data', sqlalchemy.Float),
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
sqlalchemy.Column('status', sqlalchemy.String(255)),
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
sqlalchemy.Column('mtu', sqlalchemy.Integer),
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
extend_existing=True
)
self.infoTable = sqlalchemy.Table(
'ConfigurationsInfo', self.metadata,
sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True),
sqlalchemy.Column('Info', sqlalchemy.Text),
extend_existing=True
)
self.peersHistoryEndpointTable = sqlalchemy.Table(
f'{dbName}_history_endpoint', self.metadata,
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False),
sqlalchemy.Column('time',
(sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP)),
extend_existing=True
)
self.metadata.create_all(self.engine)
def getPeers(self):
self.Peers.clear()
if self.configurationFileChanged():
with open(self.configPath, 'r') as configFile:
p = []
pCounter = -1
content = configFile.read().split('\n')
try:
if "[Peer]" not in content:
current_app.logger.info(f"{self.Name} config has no [Peer] section")
return
peerStarts = content.index("[Peer]")
content = content[peerStarts:]
for i in content:
if not RegexMatch("#(.*)", i) and not RegexMatch(";(.*)", i):
if i == "[Peer]":
pCounter += 1
p.append({})
p[pCounter]["name"] = ""
else:
if len(i) > 0:
split = re.split(r'\s*=\s*', i, 1)
if len(split) == 2:
p[pCounter][split[0]] = split[1]
if RegexMatch("#Name# = (.*)", i):
split = re.split(r'\s*=\s*', i, 1)
if len(split) == 2:
p[pCounter]["name"] = split[1]
with self.engine.begin() as conn:
for i in p:
if "PublicKey" in i.keys():
tempPeer = conn.execute(self.peersTable.select().where(
self.peersTable.columns.id == i['PublicKey']
)).mappings().fetchone()
if tempPeer is None:
tempPeer = {
"id": i['PublicKey'],
"advanced_security": i.get('AdvancedSecurity', 'off'),
"private_key": "",
"DNS": self.DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1],
"endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[
1],
"name": i.get("name"),
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": "N/A",
"status": "stopped",
"latest_handshake": "N/A",
"allowed_ip": i.get("AllowedIPs", "N/A"),
"cumu_receive": 0,
"cumu_sent": 0,
"cumu_data": 0,
"mtu": self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1],
"keepalive": self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1],
"remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
"preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else ""
}
conn.execute(
self.peersTable.insert().values(tempPeer)
)
else:
conn.execute(
self.peersTable.update().values({
"allowed_ip": i.get("AllowedIPs", "N/A")
}).where(
self.peersTable.columns.id == i['PublicKey']
)
)
self.Peers.append(AmneziaWGPeer(tempPeer, self))
except Exception as e:
current_app.logger.error(f"{self.Name} getPeers() Error", e)
else:
with self.engine.connect() as conn:
existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall()
for i in existingPeers:
self.Peers.append(AmneziaWGPeer(i, self))
def addPeers(self, peers: list) -> tuple[bool, list, str]:
result = {
"message": None,
"peers": []
}
try:
with self.engine.begin() as conn:
for i in peers:
newPeer = {
"id": i['id'],
"private_key": i['private_key'],
"DNS": i['DNS'],
"endpoint_allowed_ip": i['endpoint_allowed_ip'],
"name": i['name'],
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": "N/A",
"status": "stopped",
"latest_handshake": "N/A",
"allowed_ip": i.get("allowed_ip", "N/A"),
"cumu_receive": 0,
"cumu_sent": 0,
"cumu_data": 0,
"mtu": i['mtu'],
"keepalive": i['keepalive'],
"remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
"preshared_key": i["preshared_key"],
"advanced_security": i['advanced_security']
}
conn.execute(
self.peersTable.insert().values(newPeer)
)
for p in peers:
presharedKeyExist = len(p['preshared_key']) > 0
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
if presharedKeyExist:
with open(uid, "w+") as f:
f.write(p['preshared_key'])
subprocess.check_output(
f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
shell=True, stderr=subprocess.STDOUT)
if presharedKeyExist:
os.remove(uid)
subprocess.check_output(
f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
self.getPeers()
for p in peers:
p = self.searchPeer(p['id'])
if p[0]:
result['peers'].append(p[1])
self.DashboardWebHooks.RunWebHook("peer_created", {
"configuration": self.Name,
"peers": list(map(lambda k : k['id'], peers))
})
except Exception as e:
current_app.logger.error("Add peers error", e)
return False, [], str(e)
return True, result['peers'], ""
def getRestrictedPeers(self):
self.RestrictedPeers = []
with self.engine.connect() as conn:
restricted = conn.execute(self.peersRestrictedTable.select()).mappings().fetchall()
for i in restricted:
self.RestrictedPeers.append(AmneziaWGPeer(i, self))

View File

@ -0,0 +1,25 @@
import configparser
import os
from sqlalchemy_utils import database_exists, create_database
from flask import current_app
def ConnectionString(database) -> str:
parser = configparser.ConfigParser(strict=False)
parser.read_file(open('wg-dashboard.ini', "r+"))
sqlitePath = os.path.join("db")
if not os.path.isdir(sqlitePath):
os.mkdir(sqlitePath)
if parser.get("Database", "type") == "postgresql":
cn = f'postgresql+psycopg://{parser.get("Database", "username")}:{parser.get("Database", "password")}@{parser.get("Database", "host")}/{database}'
elif parser.get("Database", "type") == "mysql":
cn = f'mysql+pymysql://{parser.get("Database", "username")}:{parser.get("Database", "password")}@{parser.get("Database", "host")}/{database}'
else:
cn = f'sqlite:///{os.path.join(sqlitePath, f"{database}.db")}'
try:
if not database_exists(cn):
create_database(cn)
except Exception as e:
current_app.logger.error("Database error. Terminating...", e)
exit(1)
return cn

View File

@ -0,0 +1,11 @@
"""
Dashboard API Key
"""
class DashboardAPIKey:
def __init__(self, Key: str, CreatedAt: str, ExpiredAt: str):
self.Key = Key
self.CreatedAt = CreatedAt
self.ExpiredAt = ExpiredAt
def toJson(self):
return self.__dict__

View File

@ -0,0 +1,498 @@
import datetime
import hashlib
import random
import uuid
import bcrypt
import pyotp
import sqlalchemy as db
import requests
from .ConnectionString import ConnectionString
from .DashboardClientsPeerAssignment import DashboardClientsPeerAssignment
from .DashboardClientsTOTP import DashboardClientsTOTP
from .DashboardOIDC import DashboardOIDC
from .Utilities import ValidatePasswordStrength
from .DashboardLogger import DashboardLogger
from flask import session
class DashboardClients:
def __init__(self, wireguardConfigurations):
self.logger = DashboardLogger()
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.OIDC = DashboardOIDC("Client")
self.dashboardClientsTable = db.Table(
'DashboardClients', self.metadata,
db.Column('ClientID', db.String(255), nullable=False, primary_key=True),
db.Column('Email', db.String(255), nullable=False, index=True),
db.Column('Password', db.String(500)),
db.Column('TotpKey', db.String(500)),
db.Column('TotpKeyVerified', db.Integer),
db.Column('CreatedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('DeletedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)),
extend_existing=True,
)
self.dashboardOIDCClientsTable = db.Table(
'DashboardOIDCClients', self.metadata,
db.Column('ClientID', db.String(255), nullable=False, primary_key=True),
db.Column('Email', db.String(255), nullable=False, index=True),
db.Column('ProviderIssuer', db.String(500), nullable=False, index=True),
db.Column('ProviderSubject', db.String(500), nullable=False, index=True),
db.Column('CreatedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('DeletedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)),
extend_existing=True,
)
self.dashboardClientsInfoTable = db.Table(
'DashboardClientsInfo', self.metadata,
db.Column('ClientID', db.String(255), nullable=False, primary_key=True),
db.Column('Name', db.String(500)),
extend_existing=True,
)
self.dashboardClientsPasswordResetLinkTable = db.Table(
'DashboardClientsPasswordResetLinks', self.metadata,
db.Column('ResetToken', db.String(255), nullable=False, primary_key=True),
db.Column('ClientID', db.String(255), nullable=False),
db.Column('CreatedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('ExpiryDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)),
extend_existing=True
)
self.metadata.create_all(self.engine)
self.Clients = {}
self.ClientsRaw = []
self.__getClients()
self.DashboardClientsTOTP = DashboardClientsTOTP()
self.DashboardClientsPeerAssignment = DashboardClientsPeerAssignment(wireguardConfigurations)
def __getClients(self):
with self.engine.connect() as conn:
localClients = db.select(
self.dashboardClientsTable.c.ClientID,
self.dashboardClientsTable.c.Email,
db.literal_column("'Local'").label("ClientGroup")
).where(
self.dashboardClientsTable.c.DeletedDate.is_(None)
)
oidcClients = db.select(
self.dashboardOIDCClientsTable.c.ClientID,
self.dashboardOIDCClientsTable.c.Email,
self.dashboardOIDCClientsTable.c.ProviderIssuer.label("ClientGroup"),
).where(
self.dashboardOIDCClientsTable.c.DeletedDate.is_(None)
)
union = db.union(localClients, oidcClients).alias("U")
self.ClientsRaw = conn.execute(
db.select(
union,
self.dashboardClientsInfoTable.c.Name
).outerjoin(self.dashboardClientsInfoTable,
union.c.ClientID == self.dashboardClientsInfoTable.c.ClientID)
).mappings().fetchall()
groups = set(map(lambda c: c.get('ClientGroup'), self.ClientsRaw))
gr = {}
for g in groups:
gr[(g if g == 'Local' else self.OIDC.GetProviderNameByIssuer(g))] = [
dict(x) for x in list(
filter(lambda c: c.get('ClientGroup') == g, self.ClientsRaw)
)
]
self.Clients = gr
def GetAllClients(self):
self.__getClients()
return self.Clients
def GetAllClientsRaw(self):
self.__getClients()
return self.ClientsRaw
def GetClient(self, ClientID) -> dict[str, str] | None:
c = filter(lambda x: x['ClientID'] == ClientID, self.ClientsRaw)
client = next((dict(client) for client in c), None)
if client is not None:
client['ClientGroup'] = self.OIDC.GetProviderNameByIssuer(client['ClientGroup'])
return client
def GetClientProfile(self, ClientID):
with self.engine.connect() as conn:
return dict(conn.execute(
db.select(
*[c for c in self.dashboardClientsInfoTable.c if c.name != 'ClientID']
).where(
self.dashboardClientsInfoTable.c.ClientID == ClientID
)
).mappings().fetchone())
def SignIn_ValidatePassword(self, Email, Password) -> bool:
if not all([Email, Password]):
return False
existingClient = self.SignIn_UserExistence(Email)
if existingClient:
return bcrypt.checkpw(Password.encode("utf-8"), existingClient.get("Password").encode("utf-8"))
return False
def SignIn_UserExistence(self, Email):
with self.engine.connect() as conn:
existingClient = conn.execute(
self.dashboardClientsTable.select().where(
self.dashboardClientsTable.c.Email == Email
)
).mappings().fetchone()
return existingClient
def SignIn_OIDC_UserExistence(self, data: dict[str, str]):
with self.engine.connect() as conn:
existingClient = conn.execute(
self.dashboardOIDCClientsTable.select().where(
db.and_(
self.dashboardOIDCClientsTable.c.ProviderIssuer == data.get('iss'),
self.dashboardOIDCClientsTable.c.ProviderSubject == data.get('sub'),
)
)
).mappings().fetchone()
return existingClient
def SignUp_OIDC(self, data: dict[str, str]) -> tuple[bool, str] | tuple[bool, None]:
if not self.SignIn_OIDC_UserExistence(data):
with self.engine.begin() as conn:
newClientUUID = str(uuid.uuid4())
conn.execute(
self.dashboardOIDCClientsTable.insert().values({
"ClientID": newClientUUID,
"Email": data.get('email', ''),
"ProviderIssuer": data.get('iss', ''),
"ProviderSubject": data.get('sub', '')
})
)
conn.execute(
self.dashboardClientsInfoTable.insert().values({
"ClientID": newClientUUID,
"Name": data.get("name")
})
)
self.logger.log(Message=f"User {data.get('email', '')} from {data.get('iss', '')} signed up")
self.__getClients()
return True, newClientUUID
return False, "User already signed up"
def SignOut_OIDC(self):
sessionPayload = session.get('OIDCPayload')
status, oidc_config = self.OIDC.GetProviderConfiguration(session.get('SignInPayload').get("Provider"))
signOut = requests.get(
oidc_config.get("end_session_endpoint"),
params={
'id_token_hint': session.get('SignInPayload').get("Payload").get('sid')
}
)
return True
def SignIn_OIDC(self, **kwargs):
status, data = self.OIDC.VerifyToken(**kwargs)
if not status:
return False, "Sign in failed. Reason: " + data
existingClient = self.SignIn_OIDC_UserExistence(data)
if not existingClient:
status, newClientUUID = self.SignUp_OIDC(data)
session['ClientID'] = newClientUUID
else:
session['ClientID'] = existingClient.get("ClientID")
session['SignInMethod'] = 'OIDC'
session['SignInPayload'] = {
"Provider": kwargs.get('provider'),
"Payload": data
}
return True, data
def SignIn(self, Email, Password) -> tuple[bool, str]:
if not all([Email, Password]):
return False, "Please fill in all fields"
existingClient = self.SignIn_UserExistence(Email)
if existingClient:
checkPwd = self.SignIn_ValidatePassword(Email, Password)
if checkPwd:
session['SignInMethod'] = 'local'
session['Email'] = Email
session['ClientID'] = existingClient.get("ClientID")
return True, self.DashboardClientsTOTP.GenerateToken(existingClient.get("ClientID"))
return False, "Email or Password is incorrect"
def SignIn_GetTotp(self, Token: str, UserProvidedTotp: str = None) -> tuple[bool, str] or tuple[bool, None, str]:
status, data = self.DashboardClientsTOTP.GetTotp(Token)
if not status:
return False, "TOTP Token is invalid"
if UserProvidedTotp is None:
if data.get('TotpKeyVerified') is None:
return True, pyotp.totp.TOTP(data.get('TotpKey')).provisioning_uri(name=data.get('Email'),
issuer_name="WGDashboard Client")
else:
totpMatched = pyotp.totp.TOTP(data.get('TotpKey')).verify(UserProvidedTotp)
if not totpMatched:
return False, "TOTP is does not match"
else:
self.DashboardClientsTOTP.RevokeToken(Token)
if data.get('TotpKeyVerified') is None:
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsTable.update().values({
'TotpKeyVerified': 1
}).where(
self.dashboardClientsTable.c.ClientID == data.get('ClientID')
)
)
return True, None
def SignUp(self, Email, Password, ConfirmPassword) -> tuple[bool, str] or tuple[bool, None]:
try:
if not all([Email, Password, ConfirmPassword]):
return False, "Please fill in all fields"
if Password != ConfirmPassword:
return False, "Passwords does not match"
existingClient = self.SignIn_UserExistence(Email)
if existingClient:
return False, "Email already signed up"
pwStrength, msg = ValidatePasswordStrength(Password)
if not pwStrength:
return pwStrength, msg
with self.engine.begin() as conn:
newClientUUID = str(uuid.uuid4())
totpKey = pyotp.random_base32()
encodePassword = Password.encode('utf-8')
conn.execute(
self.dashboardClientsTable.insert().values({
"ClientID": newClientUUID,
"Email": Email,
"Password": bcrypt.hashpw(encodePassword, bcrypt.gensalt()).decode("utf-8"),
"TotpKey": totpKey
})
)
conn.execute(
self.dashboardClientsInfoTable.insert().values({
"ClientID": newClientUUID
})
)
self.logger.log(Message=f"User {Email} signed up")
self.__getClients()
except Exception as e:
self.logger.log(Status="false", Message=f"Signed up failed, reason: {str(e)}")
return False, "Signe up failed."
return True, None
def GetClientAssignedPeers(self, ClientID):
return self.DashboardClientsPeerAssignment.GetAssignedPeers(ClientID)
def ResetClientPassword(self, ClientID, NewPassword, ConfirmNewPassword) -> tuple[bool, str] | tuple[bool, None]:
c = self.GetClient(ClientID)
if c is None:
return False, "Client does not exist"
if NewPassword != ConfirmNewPassword:
return False, "New passwords does not match"
pwStrength, msg = ValidatePasswordStrength(NewPassword)
if not pwStrength:
return pwStrength, msg
try:
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsTable.update().values({
"TotpKeyVerified": None,
"TotpKey": pyotp.random_base32(),
"Password": bcrypt.hashpw(NewPassword.encode('utf-8'), bcrypt.gensalt()).decode("utf-8"),
}).where(
self.dashboardClientsTable.c.ClientID == ClientID
)
)
self.logger.log(Message=f"User {ClientID} reset password and TOTP")
except Exception as e:
self.logger.log(Status="false", Message=f"User {ClientID} reset password failed, reason: {str(e)}")
return False, "Reset password failed."
return True, None
def UpdateClientPassword(self, ClientID, CurrentPassword, NewPassword, ConfirmNewPassword) -> tuple[bool, str] | tuple[bool, None]:
c = self.GetClient(ClientID)
if c is None:
return False, "Client does not exist"
if not all([CurrentPassword, NewPassword, ConfirmNewPassword]):
return False, "Please fill in all fields"
if not self.SignIn_ValidatePassword(c.get('Email'), CurrentPassword):
return False, "Current password does not match"
if NewPassword != ConfirmNewPassword:
return False, "New passwords does not match"
pwStrength, msg = ValidatePasswordStrength(NewPassword)
if not pwStrength:
return pwStrength, msg
try:
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsTable.update().values({
"Password": bcrypt.hashpw(NewPassword.encode('utf-8'), bcrypt.gensalt()).decode("utf-8"),
}).where(
self.dashboardClientsTable.c.ClientID == ClientID
)
)
self.logger.log(Message=f"User {ClientID} updated password")
except Exception as e:
self.logger.log(Status="false", Message=f"User {ClientID} update password failed, reason: {str(e)}")
return False, "Update password failed."
return True, None
def UpdateClientProfile(self, ClientID, Name):
try:
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsInfoTable.update().values({
"Name": Name
}).where(
self.dashboardClientsInfoTable.c.ClientID == ClientID
)
)
self.logger.log(Message=f"User {ClientID} updated name to {Name}")
except Exception as e:
self.logger.log(Status="false", Message=f"User {ClientID} updated name to {Name} failed")
return False
return True
def DeleteClient(self, ClientID):
try:
with self.engine.begin() as conn:
client = self.GetClient(ClientID)
if client.get("ClientGroup") == "Local":
conn.execute(
self.dashboardClientsTable.delete().where(
self.dashboardClientsTable.c.ClientID == ClientID
)
)
else:
conn.execute(
self.dashboardOIDCClientsTable.delete().where(
self.dashboardOIDCClientsTable.c.ClientID == ClientID
)
)
conn.execute(
self.dashboardClientsInfoTable.delete().where(
self.dashboardClientsInfoTable.c.ClientID == ClientID
)
)
self.DashboardClientsPeerAssignment.UnassignPeers(ClientID)
self.__getClients()
except Exception as e:
self.logger.log(Status="false", Message=f"Failed to delete {ClientID}")
return False
return True
'''
For WGDashboard Admin to Manage Clients
'''
def GenerateClientPasswordResetToken(self, ClientID) -> bool | str:
c = self.GetClient(ClientID)
if c is None:
return False
newToken = str(random.randint(0, 999999)).zfill(6)
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsPasswordResetLinkTable.update().values({
"ExpiryDate": datetime.datetime.now()
}).where(
db.and_(
self.dashboardClientsPasswordResetLinkTable.c.ClientID == ClientID,
self.dashboardClientsPasswordResetLinkTable.c.ExpiryDate > db.func.now()
)
)
)
conn.execute(
self.dashboardClientsPasswordResetLinkTable.insert().values({
"ResetToken": newToken,
"ClientID": ClientID,
"CreatedDate": datetime.datetime.now(),
"ExpiryDate": datetime.datetime.now() + datetime.timedelta(minutes=30)
})
)
return newToken
def ValidateClientPasswordResetToken(self, ClientID, Token):
c = self.GetClient(ClientID)
if c is None:
return False
with self.engine.connect() as conn:
t = conn.execute(
self.dashboardClientsPasswordResetLinkTable.select().where(
db.and_(self.dashboardClientsPasswordResetLinkTable.c.ClientID == ClientID,
self.dashboardClientsPasswordResetLinkTable.c.ResetToken == Token,
self.dashboardClientsPasswordResetLinkTable.c.ExpiryDate > datetime.datetime.now())
)
).mappings().fetchone()
return t is not None
def RevokeClientPasswordResetToken(self, ClientID, Token):
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsPasswordResetLinkTable.update().values({
"ExpiryDate": datetime.datetime.now()
}).where(
db.and_(self.dashboardClientsPasswordResetLinkTable.c.ClientID == ClientID,
self.dashboardClientsPasswordResetLinkTable.c.ResetToken == Token)
)
)
return True
def GetAssignedPeerClients(self, ConfigurationName, PeerID):
c = self.DashboardClientsPeerAssignment.GetAssignedClients(ConfigurationName, PeerID)
for a in c:
client = self.GetClient(a.ClientID)
if client is not None:
a.Client = self.GetClient(a.ClientID)
return c
def GetClientAssignedPeersGrouped(self, ClientID):
client = self.GetClient(ClientID)
if client is not None:
p = self.DashboardClientsPeerAssignment.GetAssignedPeers(ClientID)
configs = set(map(lambda x : x['configuration_name'], p))
d = {}
for i in configs:
d[i] = list(filter(lambda x : x['configuration_name'] == i, p))
return d
return None
def AssignClient(self, ConfigurationName, PeerID, ClientID) -> tuple[bool, dict[str, str]] | tuple[bool, None]:
return self.DashboardClientsPeerAssignment.AssignClient(ClientID, ConfigurationName, PeerID)
def UnassignClient(self, AssignmentID):
return self.DashboardClientsPeerAssignment.UnassignClients(AssignmentID)

View File

@ -0,0 +1,159 @@
import datetime
import uuid
from .ConnectionString import ConnectionString
from .DashboardLogger import DashboardLogger
import sqlalchemy as db
from .WireguardConfiguration import WireguardConfiguration
class Assignment:
def __init__(self, **kwargs):
self.AssignmentID: str = kwargs.get('AssignmentID')
self.ClientID: str = kwargs.get('ClientID')
self.ConfigurationName: str = kwargs.get('ConfigurationName')
self.PeerID: str = kwargs.get('PeerID')
self.AssignedDate: datetime.datetime = kwargs.get('AssignedDate')
self.UnassignedDate: datetime.datetime = kwargs.get('UnassignedDate')
self.Client: dict = {
"ClientID": self.ClientID
}
def toJson(self):
return {
"AssignmentID": self.AssignmentID,
"Client": self.Client,
"ConfigurationName": self.ConfigurationName,
"PeerID": self.PeerID,
"AssignedDate": self.AssignedDate.strftime("%Y-%m-%d %H:%M:%S"),
"UnassignedDate": self.UnassignedDate.strftime("%Y-%m-%d %H:%M:%S") if self.UnassignedDate is not None else self.UnassignedDate
}
class DashboardClientsPeerAssignment:
def __init__(self, wireguardConfigurations: dict[str, WireguardConfiguration]):
self.logger = DashboardLogger()
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.wireguardConfigurations = wireguardConfigurations
self.dashboardClientsPeerAssignmentTable = db.Table(
'DashboardClientsPeerAssignment', self.metadata,
db.Column('AssignmentID', db.String(255), nullable=False, primary_key=True),
db.Column('ClientID', db.String(255), nullable=False, index=True),
db.Column('ConfigurationName', db.String(255)),
db.Column('PeerID', db.String(500)),
db.Column('AssignedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('UnassignedDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)),
extend_existing=True
)
self.metadata.create_all(self.engine)
self.assignments: list[Assignment] = []
self.__getAssignments()
def __getAssignments(self):
with self.engine.connect() as conn:
assignments = []
get = conn.execute(
self.dashboardClientsPeerAssignmentTable.select().where(
self.dashboardClientsPeerAssignmentTable.c.UnassignedDate.is_(None)
)
).mappings().fetchall()
for a in get:
assignments.append(Assignment(**a))
self.assignments = assignments
def AssignClient(self, ClientID, ConfigurationName, PeerID):
existing = list(
filter(lambda e:
e.ClientID == ClientID and
e.ConfigurationName == ConfigurationName and
e.PeerID == PeerID, self.assignments)
)
if len(existing) == 0:
if ConfigurationName in self.wireguardConfigurations.keys():
config = self.wireguardConfigurations.get(ConfigurationName)
peer = list(filter(lambda x : x.id == PeerID, config.Peers))
if len(peer) == 1:
with self.engine.begin() as conn:
data = {
"AssignmentID": str(uuid.uuid4()),
"ClientID": ClientID,
"ConfigurationName": ConfigurationName,
"PeerID": PeerID
}
conn.execute(
self.dashboardClientsPeerAssignmentTable.insert().values(data)
)
self.__getAssignments()
return True, data
return False, None
def UnassignClients(self, AssignmentID):
existing = list(
filter(lambda e:
e.AssignmentID == AssignmentID, self.assignments)
)
if not existing:
return False
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsPeerAssignmentTable.update().values({
"UnassignedDate": datetime.datetime.now()
}).where(
self.dashboardClientsPeerAssignmentTable.c.AssignmentID == AssignmentID
)
)
self.__getAssignments()
return True
def UnassignPeers(self, ClientID):
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsPeerAssignmentTable.update().values({
"UnassignedDate": datetime.datetime.now()
}).where(
db.and_(
self.dashboardClientsPeerAssignmentTable.c.ClientID == ClientID,
self.dashboardClientsPeerAssignmentTable.c.UnassignedDate.is_(db.null())
)
)
)
self.__getAssignments()
return True
def GetAssignedClients(self, ConfigurationName, PeerID) -> list[Assignment]:
self.__getAssignments()
return list(filter(
lambda c : c.ConfigurationName == ConfigurationName and
c.PeerID == PeerID, self.assignments))
def GetAssignedPeers(self, ClientID):
self.__getAssignments()
peers = []
assigned = filter(lambda e:
e.ClientID == ClientID, self.assignments)
for a in assigned:
peer = filter(lambda e : e.id == a.PeerID,
self.wireguardConfigurations[a.ConfigurationName].Peers)
for p in peer:
peers.append({
'assignment_id': a.AssignmentID,
'protocol': self.wireguardConfigurations[a.ConfigurationName].Protocol,
'id': p.id,
'private_key': p.private_key,
'name': p.name,
'received_data': p.total_receive + p.cumu_receive,
'sent_data': p.total_sent + p.cumu_sent,
'data': p.total_data + p.cumu_data,
'status': p.status,
'latest_handshake': p.latest_handshake,
'allowed_ip': p.allowed_ip,
'jobs': p.jobs,
'configuration_name': a.ConfigurationName,
'peer_configuration_data': p.downloadPeer()
})
return peers

View File

@ -0,0 +1,82 @@
import datetime
import hashlib
import uuid
import sqlalchemy as db
from .ConnectionString import ConnectionString
class DashboardClientsTOTP:
def __init__(self):
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.dashboardClientsTOTPTable = db.Table(
'DashboardClientsTOTPTokens', self.metadata,
db.Column("Token", db.String(500), primary_key=True, index=True),
db.Column("ClientID", db.String(500), index=True),
db.Column(
"ExpireTime", (db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)
)
)
self.metadata.create_all(self.engine)
self.metadata.reflect(self.engine)
self.dashboardClientsTable = self.metadata.tables['DashboardClients']
def GenerateToken(self, ClientID) -> str:
token = hashlib.sha512(f"{ClientID}_{datetime.datetime.now()}_{uuid.uuid4()}".encode()).hexdigest()
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsTOTPTable.update().values({
"ExpireTime": datetime.datetime.now()
}).where(
db.and_(self.dashboardClientsTOTPTable.c.ClientID == ClientID, self.dashboardClientsTOTPTable.c.ExpireTime > datetime.datetime.now())
)
)
conn.execute(
self.dashboardClientsTOTPTable.insert().values({
"Token": token,
"ClientID": ClientID,
"ExpireTime": datetime.datetime.now() + datetime.timedelta(minutes=10)
})
)
return token
def RevokeToken(self, Token) -> bool:
try:
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsTOTPTable.update().values({
"ExpireTime": datetime.datetime.now()
}).where(
self.dashboardClientsTOTPTable.c.Token == Token
)
)
except Exception as e:
return False
return True
def GetTotp(self, token: str) -> tuple[bool, dict] or tuple[bool, None]:
with self.engine.connect() as conn:
totp = conn.execute(
db.select(
self.dashboardClientsTable.c.ClientID,
self.dashboardClientsTable.c.Email,
self.dashboardClientsTable.c.TotpKey,
self.dashboardClientsTable.c.TotpKeyVerified,
).select_from(
self.dashboardClientsTOTPTable
).where(
db.and_(
self.dashboardClientsTOTPTable.c.Token == token,
self.dashboardClientsTOTPTable.c.ExpireTime > datetime.datetime.now()
)
).join(
self.dashboardClientsTable,
self.dashboardClientsTOTPTable.c.ClientID == self.dashboardClientsTable.c.ClientID
)
).mappings().fetchone()
if totp:
return True, dict(totp)
return False, None

View File

@ -0,0 +1,285 @@
"""
Dashboard Configuration
"""
import configparser, secrets, os, pyotp, ipaddress, bcrypt
from sqlalchemy_utils import database_exists, create_database
import sqlalchemy as db
from datetime import datetime
from typing import Any
from flask import current_app
from .ConnectionString import ConnectionString
from .Utilities import (
GetRemoteEndpoint, ValidateDNSAddress
)
from .DashboardAPIKey import DashboardAPIKey
class DashboardConfig:
DashboardVersion = 'v4.3.1'
ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.')
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard.ini')
def __init__(self):
if not os.path.exists(DashboardConfig.ConfigurationFilePath):
open(DashboardConfig.ConfigurationFilePath, "x")
self.__config = configparser.RawConfigParser(strict=False)
self.__config.read_file(open(DashboardConfig.ConfigurationFilePath, "r+"))
self.hiddenAttribute = ["totp_key", "auth_req"]
self.__default = {
"Account": {
"username": "admin",
"password": "admin",
"enable_totp": "false",
"totp_verified": "false",
"totp_key": pyotp.random_base32()
},
"Server": {
"wg_conf_path": "/etc/wireguard",
"awg_conf_path": "/etc/amnezia/amneziawg",
"app_prefix": "",
"app_ip": "0.0.0.0",
"app_port": "10086",
"auth_req": "true",
"version": DashboardConfig.DashboardVersion,
"dashboard_refresh_interval": "60000",
"dashboard_peer_list_display": "grid",
"dashboard_sort": "status",
"dashboard_theme": "dark",
"dashboard_api_key": "false",
"dashboard_language": "en-US"
},
"Peers": {
"peer_global_DNS": "1.1.1.1",
"peer_endpoint_allowed_ip": "0.0.0.0/0",
"peer_display_mode": "grid",
"remote_endpoint": GetRemoteEndpoint(),
"peer_MTU": "1420",
"peer_keep_alive": "21"
},
"Other": {
"welcome_session": "true"
},
"Database":{
"type": "sqlite",
"host": "",
"port": "",
"username": "",
"password": ""
},
"Email":{
"server": "",
"port": "",
"encryption": "",
"username": "",
"email_password": "",
"authentication_required": "true",
"send_from": "",
"email_template": ""
},
"OIDC": {
"admin_enable": "false",
"client_enable": "false"
},
"Clients": {
"enable": "true",
},
"WireGuardConfiguration": {
"autostart": ""
}
}
for section, keys in self.__default.items():
for key, value in keys.items():
exist, currentData = self.GetConfig(section, key)
if not exist:
self.SetConfig(section, key, value, True)
self.engine = db.create_engine(ConnectionString('wgdashboard'))
self.dbMetadata = db.MetaData()
self.__createAPIKeyTable()
self.DashboardAPIKeys = self.__getAPIKeys()
self.APIAccessed = False
self.SetConfig("Server", "version", DashboardConfig.DashboardVersion)
def getConnectionString(self, database) -> str or None:
sqlitePath = os.path.join(DashboardConfig.ConfigurationPath, "db")
if not os.path.isdir(sqlitePath):
os.mkdir(sqlitePath)
if self.GetConfig("Database", "type")[1] == "postgresql":
cn = f'postgresql+psycopg2://{self.GetConfig("Database", "username")[1]}:{self.GetConfig("Database", "password")[1]}@{self.GetConfig("Database", "host")[1]}/{database}'
elif self.GetConfig("Database", "type")[1] == "mysql":
cn = f'mysql+mysqldb://{self.GetConfig("Database", "username")[1]}:{self.GetConfig("Database", "password")[1]}@{self.GetConfig("Database", "host")[1]}/{database}'
else:
cn = f'sqlite:///{os.path.join(sqlitePath, f"{database}.db")}'
if not database_exists(cn):
create_database(cn)
return cn
def __createAPIKeyTable(self):
self.apiKeyTable = db.Table('DashboardAPIKeys', self.dbMetadata,
db.Column("Key", db.String(255), nullable=False, primary_key=True),
db.Column("CreatedAt",
(db.DATETIME if self.GetConfig('Database', 'type')[1] == 'sqlite' else db.TIMESTAMP),
server_default=db.func.now()
),
db.Column("ExpiredAt",
(db.DATETIME if self.GetConfig('Database', 'type')[1] == 'sqlite' else db.TIMESTAMP)
)
)
self.dbMetadata.create_all(self.engine)
def __getAPIKeys(self) -> list[DashboardAPIKey]:
try:
with self.engine.connect() as conn:
keys = conn.execute(self.apiKeyTable.select().where(
db.or_(self.apiKeyTable.columns.ExpiredAt.is_(None), self.apiKeyTable.columns.ExpiredAt > datetime.now())
)).fetchall()
fKeys = []
for k in keys:
fKeys.append(DashboardAPIKey(k[0], k[1].strftime("%Y-%m-%d %H:%M:%S"), (k[2].strftime("%Y-%m-%d %H:%M:%S") if k[2] else None)))
return fKeys
except Exception as e:
current_app.logger.error("API Keys error", e)
return []
def createAPIKeys(self, ExpiredAt = None):
newKey = secrets.token_urlsafe(32)
with self.engine.begin() as conn:
conn.execute(
self.apiKeyTable.insert().values({
"Key": newKey,
"ExpiredAt": ExpiredAt
})
)
self.DashboardAPIKeys = self.__getAPIKeys()
def deleteAPIKey(self, key):
with self.engine.begin() as conn:
conn.execute(
self.apiKeyTable.update().values({
"ExpiredAt": datetime.now(),
}).where(self.apiKeyTable.columns.Key == key)
)
self.DashboardAPIKeys = self.__getAPIKeys()
def __configValidation(self, section : str, key: str, value: Any) -> tuple[bool, str]:
if (type(value) is str and len(value) == 0
and section not in ['Email', 'WireGuardConfiguration'] and
(section == 'Peer' and key == 'peer_global_dns')):
return False, "Field cannot be empty!"
if section == "Peers" and key == "peer_global_dns" and len(value) > 0:
return ValidateDNSAddress(value)
if section == "Peers" and key == "peer_endpoint_allowed_ip":
value = value.split(",")
for i in value:
i = i.strip()
try:
ipaddress.ip_network(i, strict=False)
except Exception as e:
return False, str(e)
if section == "Server" and key == "wg_conf_path":
if not os.path.exists(value):
return False, f"{value} is not a valid path"
if section == "Account" and key == "password":
if self.GetConfig("Account", "password")[0]:
if not self.__checkPassword(
value["currentPassword"], self.GetConfig("Account", "password")[1].encode("utf-8")):
return False, "Current password does not match."
if value["newPassword"] != value["repeatNewPassword"]:
return False, "New passwords does not match"
return True, ""
def generatePassword(self, plainTextPassword: str):
return bcrypt.hashpw(plainTextPassword.encode("utf-8"), bcrypt.gensalt())
def __checkPassword(self, plainTextPassword: str, hashedPassword: bytes):
return bcrypt.checkpw(plainTextPassword.encode("utf-8"), hashedPassword)
def SetConfig(self, section: str, key: str, value: str | bool | list[str] | dict[str, str], init: bool = False) -> tuple[bool, str] | tuple[bool, None]:
if key in self.hiddenAttribute and not init:
return False, None
if not init:
valid, msg = self.__configValidation(section, key, value)
if not valid:
return False, msg
if section == "Account" and key == "password":
if not init:
value = self.generatePassword(value["newPassword"]).decode("utf-8")
else:
value = self.generatePassword(value).decode("utf-8")
if section == "Email" and key == "email_template":
value = value.encode('unicode_escape').decode('utf-8')
if section == "Server" and key == "wg_conf_path":
if not os.path.exists(value):
return False, "Path does not exist"
if section not in self.__config:
if init:
self.__config[section] = {}
else:
return False, "Section does not exist"
if ((key not in self.__config[section].keys() and init) or
(key in self.__config[section].keys())):
if type(value) is bool:
if value:
self.__config[section][key] = "true"
else:
self.__config[section][key] = "false"
elif type(value) in [int, float]:
self.__config[section][key] = str(value)
elif type(value) is list:
self.__config[section][key] = "||".join(value).strip("||")
else:
self.__config[section][key] = fr"{value}"
return self.SaveConfig(), ""
else:
return False, f"{key} does not exist under {section}"
def SaveConfig(self) -> bool:
try:
with open(DashboardConfig.ConfigurationFilePath, "w+", encoding='utf-8') as configFile:
self.__config.write(configFile)
return True
except Exception as e:
return False
def GetConfig(self, section, key) ->tuple[bool, bool] | tuple[bool, str] | tuple[bool, list[str]] | tuple[bool, None]:
if section not in self.__config:
return False, None
if key not in self.__config[section]:
return False, None
if section == "Email" and key == "email_template":
return True, self.__config[section][key].encode('utf-8').decode('unicode_escape')
if section == "WireGuardConfiguration" and key == "autostart":
return True, list(filter(lambda x: len(x) > 0, self.__config[section][key].split("||")))
if self.__config[section][key] in ["1", "yes", "true", "on"]:
return True, True
if self.__config[section][key] in ["0", "no", "false", "off"]:
return True, False
return True, self.__config[section][key]
def toJson(self) -> dict[str, dict[Any, Any]]:
the_dict = {}
for section in self.__config.sections():
the_dict[section] = {}
for key, val in self.__config.items(section):
if key not in self.hiddenAttribute:
the_dict[section][key] = self.GetConfig(section, key)[1]
return the_dict

View File

@ -0,0 +1,44 @@
"""
Dashboard Logger Class
"""
import uuid
import sqlalchemy as db
from flask import current_app
from .ConnectionString import ConnectionString
class DashboardLogger:
def __init__(self):
self.engine = db.create_engine(ConnectionString("wgdashboard_log"))
self.metadata = db.MetaData()
self.dashboardLoggerTable = db.Table('DashboardLog', self.metadata,
db.Column('LogID', db.String(255), nullable=False, primary_key=True),
db.Column('LogDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('URL', db.String(255)),
db.Column('IP', db.String(255)),
db.Column('Status', db.String(255), nullable=False),
db.Column('Message', db.Text), extend_existing=True,
)
self.metadata.create_all(self.engine)
self.log(Message="WGDashboard started")
def log(self, URL: str = "", IP: str = "", Status: str = "true", Message: str = "") -> bool:
try:
with self.engine.begin() as conn:
conn.execute(
self.dashboardLoggerTable.insert().values(
LogID=str(uuid.uuid4()),
URL=URL,
IP=IP,
Status=Status,
Message=Message
)
)
return True
except Exception as e:
current_app.logger.error(f"Access Log Error", e)
return False

View File

@ -0,0 +1,142 @@
import os
import json
import requests
from jose import jwt
import certifi
from flask import current_app
class DashboardOIDC:
ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.')
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard-oidc-providers.json')
def __init__(self, mode):
self.mode = mode
self.providers: dict[str, dict] = {}
self.provider_secret: dict[str, str] = {}
self.__default = {
"Admin": {
'Provider': {
'client_id': '',
'client_secret': '',
'issuer': '',
},
},
"Client": {
'Provider': {
'client_id': '',
'client_secret': '',
'issuer': '',
},
}
}
if not os.path.exists(DashboardOIDC.ConfigurationFilePath):
with open(DashboardOIDC.ConfigurationFilePath, "w+") as f:
encoder = json.JSONEncoder(indent=4)
f.write(encoder.encode(self.__default))
self.ReadFile()
def GetProviders(self):
return self.providers
def GetProviderNameByIssuer(self, issuer):
for (key, val) in self.providers.items():
if val.get('openid_configuration').get('issuer') == issuer:
return key
return issuer
def VerifyToken(self, provider, code, redirect_uri):
try:
if not all([provider, code, redirect_uri]):
return False, "Please provide all parameters"
if provider not in self.providers.keys():
return False, "Provider does not exist"
secrete = self.provider_secret.get(provider)
oidc_config_status, oidc_config = self.GetProviderConfiguration(provider)
provider_info = self.providers.get(provider)
data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": redirect_uri,
"client_id": provider_info.get('client_id'),
"client_secret": secrete
}
try:
tokens = requests.post(oidc_config.get('token_endpoint'), data=data).json()
if not all([tokens.get('access_token'), tokens.get('id_token')]):
return False, tokens.get('error_description', None)
except Exception as e:
current_app.logger.error("Verify token failed", e)
return False, str(e)
access_token = tokens.get('access_token')
id_token = tokens.get('id_token')
jwks_uri = oidc_config.get("jwks_uri")
issuer = oidc_config.get("issuer")
jwks = requests.get(jwks_uri, verify=certifi.where()).json()
headers = jwt.get_unverified_header(id_token)
kid = headers["kid"]
key = next(k for k in jwks["keys"] if k["kid"] == kid)
payload = jwt.decode(
id_token,
key,
algorithms=[key["alg"]],
audience=provider_info.get('client_id'),
issuer=issuer,
access_token=access_token
)
print(payload)
return True, payload
except Exception as e:
current_app.logger.error('Read OIDC file failed. Reason: ' + str(e), provider, code, redirect_uri)
return False, str(e)
def GetProviderConfiguration(self, provider_name):
if not all([provider_name]):
return False, None
provider = self.providers.get(provider_name)
try:
oidc_config = requests.get(
f"{provider.get('issuer').strip('/')}/.well-known/openid-configuration",
verify=certifi.where()
).json()
except Exception as e:
current_app.logger.error("Failed to get OpenID Configuration of " + provider.get('issuer'), exc_info=e)
return False, None
return True, oidc_config
def ReadFile(self):
decoder = json.JSONDecoder()
try:
providers = decoder.decode(
open(DashboardOIDC.ConfigurationFilePath, 'r').read()
)
providers = providers[self.mode]
for k in providers.keys():
if all([providers[k]['client_id'], providers[k]['client_secret'], providers[k]['issuer']]):
try:
oidc_config = requests.get(
f"{providers[k]['issuer'].strip('/')}/.well-known/openid-configuration",
timeout=3,
verify=certifi.where()
).json()
self.providers[k] = {
'client_id': providers[k]['client_id'],
'issuer': providers[k]['issuer'].strip('/'),
'openid_configuration': oidc_config
}
self.provider_secret[k] = providers[k]['client_secret']
current_app.logger.info(f"Registered OIDC Provider: {k}")
except Exception as e:
current_app.logger.error(f"Failed to register OIDC config for {k}", exc_info=e)
except Exception as e:
current_app.logger.error('Read OIDC file failed. Reason: ' + str(e))
return False

View File

@ -0,0 +1,117 @@
import os
import sys
import importlib.util
from pathlib import Path
from typing import Dict, Callable, List, Optional
import threading
class DashboardPlugins:
def __init__(self, app, WireguardConfigurations, directory: str = 'plugins'):
self.directory = Path('plugins')
self.loadedPlugins: dict[str, Callable] = {}
self.errorPlugins: List[str] = []
self.logger = app.logger
self.WireguardConfigurations = WireguardConfigurations
def startThreads(self):
self.loadAllPlugins()
self.executeAllPlugins()
def preparePlugins(self) -> list[Path]:
readyPlugins = []
if not self.directory.exists():
os.mkdir(self.directory)
return []
for plugin in self.directory.iterdir():
if plugin.is_dir():
codeFile = plugin / "main.py"
if codeFile.exists():
self.logger.info(f"Prepared plugin: {plugin.name}")
readyPlugins.append(plugin)
return readyPlugins
def loadPlugin(self, path: Path) -> Optional[Callable]:
pluginName = path.name
codeFile = path / "main.py"
try:
spec = importlib.util.spec_from_file_location(
f"WGDashboardPlugin_{pluginName}",
codeFile
)
if spec is None or spec.loader is None:
raise ImportError(f"Failed to create spec for {pluginName}")
module = importlib.util.module_from_spec(spec)
plugin_dir_str = str(path)
if plugin_dir_str not in sys.path:
sys.path.insert(0, plugin_dir_str)
try:
spec.loader.exec_module(module)
finally:
if plugin_dir_str in sys.path:
sys.path.remove(plugin_dir_str)
if hasattr(module, 'main'):
main_func = getattr(module, 'main')
if callable(main_func):
self.logger.info(f"Successfully loaded plugin [{pluginName}]")
return main_func
else:
raise AttributeError(f"'main' in {pluginName} is not callable")
else:
raise AttributeError(f"Plugin {pluginName} does not have a 'main' function")
except Exception as e:
self.logger.error(f"Failed to load the plugin [{pluginName}]. Reason: {str(e)}")
self.errorPlugins.append(pluginName)
return None
def loadAllPlugins(self):
self.loadedPlugins.clear()
self.errorPlugins.clear()
preparedPlugins = self.preparePlugins()
for plugin in preparedPlugins:
pluginName = plugin.name
mainFunction = self.loadPlugin(plugin)
if mainFunction:
self.loadedPlugins[pluginName] = mainFunction
if self.errorPlugins:
self.logger.warning(f"Failed to load {len(self.errorPlugins)} plugin(s): {self.errorPlugins}")
def executePlugin(self, pluginName: str):
if pluginName not in self.loadedPlugins.keys():
self.logger.error(f"Failed to execute plugin [{pluginName}]. Reason: Not loaded")
return False
plugin = self.loadedPlugins.get(pluginName)
try:
t = threading.Thread(target=plugin, args=(self.WireguardConfigurations,), daemon=True)
t.name = f'WGDashboardPlugin_{pluginName}'
t.start()
if t.is_alive():
self.logger.info(f"Execute plugin [{pluginName}] success. PID: {t.native_id}")
except Exception as e:
self.logger.error(f"Failed to execute plugin [{pluginName}]. Reason: {str(e)}")
return False
return True
def executeAllPlugins(self):
for plugin in self.loadedPlugins.keys():
self.executePlugin(plugin)

View File

@ -0,0 +1,287 @@
import json
import threading
import time
import urllib.parse
import uuid
from datetime import datetime, timedelta
import requests
from pydantic import BaseModel, field_serializer
import sqlalchemy as db
from .ConnectionString import ConnectionString
from flask import current_app
WebHookActions = ['peer_created', 'peer_deleted', 'peer_updated']
class WebHook(BaseModel):
WebHookID: str = ''
PayloadURL: str = ''
ContentType: str = 'application/json'
Headers: dict[str, dict[str, str]] = {}
VerifySSL: bool = True
SubscribedActions: list[str] = WebHookActions
IsActive: bool = True
CreationDate: datetime = ''
Notes: str = ''
class WebHookSessionLog(BaseModel):
LogTime: datetime
Status: int
Message: str = ''
@field_serializer('LogTime')
def logTimeSerializer(self, LogTime: datetime):
return LogTime.strftime("%Y-%m-%d %H:%M:%S")
class WebHookSessionLogs(BaseModel):
Logs: list[WebHookSessionLog] = []
def addLog(self, status: int, message: str):
self.Logs.append(WebHookSessionLog(LogTime=datetime.now(), Status=status, Message=message))
class DashboardWebHooks:
def __init__(self, DashboardConfig):
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.webHooksTable = db.Table(
'DashboardWebHooks', self.metadata,
db.Column('WebHookID', db.String(255), nullable=False, primary_key=True),
db.Column('PayloadURL', db.Text, nullable=False),
db.Column('ContentType', db.String(255), nullable=False),
db.Column('Headers', db.JSON),
db.Column('VerifySSL', db.Boolean, nullable=False),
db.Column('SubscribedActions', db.JSON),
db.Column('IsActive', db.Boolean, nullable=False),
db.Column('CreationDate',
(db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
server_default=db.func.now(),
nullable=False),
db.Column('Notes', db.Text),
extend_existing=True
)
self.webHookSessionsTable = db.Table(
'DashboardWebHookSessions', self.metadata,
db.Column('WebHookSessionID', db.String(255), nullable=False, primary_key=True),
db.Column('WebHookID', db.String(255), nullable=False),
db.Column('StartDate',
(db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
server_default=db.func.now(),
nullable=False
),
db.Column('EndDate',
(db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
),
db.Column('Data', db.JSON),
db.Column('Status', db.INTEGER),
db.Column('Logs', db.JSON)
)
self.metadata.create_all(self.engine)
self.WebHooks: list[WebHook] = []
with self.engine.begin() as conn:
conn.execute(
self.webHookSessionsTable.update().values({
"EndDate": datetime.now(),
"Status": 2
}).where(
self.webHookSessionsTable.c.Status == -1
)
)
self.__getWebHooks()
def __getWebHooks(self):
with self.engine.connect() as conn:
webhooks = conn.execute(
self.webHooksTable.select().order_by(
self.webHooksTable.c.CreationDate
)
).mappings().fetchall()
self.WebHooks.clear()
self.WebHooks = [WebHook(**webhook) for webhook in webhooks]
def GetWebHooks(self):
self.__getWebHooks()
return list(map(lambda x : x.model_dump(), self.WebHooks))
def GetWebHookSessions(self, webHook: WebHook):
with self.engine.connect() as conn:
sessions = conn.execute(
self.webHookSessionsTable.select().where(
self.webHookSessionsTable.c.WebHookID == webHook.WebHookID
).order_by(
db.desc(self.webHookSessionsTable.c.StartDate)
)
).mappings().fetchall()
return sessions
def CreateWebHook(self) -> WebHook:
return WebHook(WebHookID=str(uuid.uuid4()))
def SearchWebHook(self, webHook: WebHook) -> WebHook | None:
try:
first = next(filter(lambda x : x.WebHookID == webHook.WebHookID, self.WebHooks))
except StopIteration:
return None
return first
def SearchWebHookByID(self, webHookID: str) -> WebHook | None:
try:
first = next(filter(lambda x : x.WebHookID == webHookID, self.WebHooks))
except StopIteration:
return None
return first
def UpdateWebHook(self, webHook: dict[str, str]) -> tuple[bool, str] | tuple[bool, None]:
try:
webHook = WebHook(**webHook)
if len(webHook.PayloadURL) == 0:
return False, "Payload URL cannot be empty"
if len(webHook.ContentType) == 0 or webHook.ContentType not in [
'application/json', 'application/x-www-form-urlencoded'
]:
return False, "Content Type is invalid"
with self.engine.begin() as conn:
if self.SearchWebHook(webHook):
conn.execute(
self.webHooksTable.update().values(
webHook.model_dump(exclude={'WebHookID'})
).where(
self.webHooksTable.c.WebHookID == webHook.WebHookID
)
)
else:
webHook.CreationDate = datetime.now()
conn.execute(
self.webHooksTable.insert().values(
webHook.model_dump()
)
)
self.__getWebHooks()
except Exception as e:
return False, str(e)
return True, None
def DeleteWebHook(self, webHook) -> tuple[bool, str] | tuple[bool, None]:
try:
webHook = WebHook(**webHook)
with self.engine.begin() as conn:
conn.execute(
self.webHooksTable.delete().where(
self.webHooksTable.c.WebHookID == webHook.WebHookID
)
)
self.__getWebHooks()
except Exception as e:
return False, str(e)
return True, None
def RunWebHook(self, action: str, data):
try:
if action not in WebHookActions:
return False
self.__getWebHooks()
subscribedWebHooks = filter(lambda webhook: action in webhook.SubscribedActions and webhook.IsActive,
self.WebHooks)
data['action'] = action
for i in subscribedWebHooks:
try:
ws = WebHookSession(i, data)
t = threading.Thread(target=ws.Execute, daemon=True)
t.start()
current_app.logger.info(f"Requesting {i.PayloadURL}")
except Exception as e:
current_app.logger.error(f"Requesting {i.PayloadURL} error", e)
except Exception as e:
current_app.logger.error("Error when running WebHook")
class WebHookSession:
def __init__(self, webHook: WebHook, data: dict[str, str]):
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.webHookSessionsTable = db.Table('DashboardWebHookSessions', self.metadata, autoload_with=self.engine)
self.webHook = webHook
self.sessionID = str(uuid.uuid4())
self.webHookSessionLogs: WebHookSessionLogs = WebHookSessionLogs()
self.time = datetime.now()
data['time'] = self.time.strftime("%Y-%m-%d %H:%M:%S")
data['webhook_id'] = webHook.WebHookID
data['webhook_session'] = self.sessionID
self.data = data
self.Prepare()
def Prepare(self):
with self.engine.begin() as conn:
conn.execute(
self.webHookSessionsTable.insert().values({
"WebHookSessionID": self.sessionID,
"WebHookID": self.webHook.WebHookID,
"Data": self.data,
"StartDate": self.time,
"Status": -1,
"Logs": self.webHookSessionLogs.model_dump()
})
)
self.UpdateSessionLog(-1, "Preparing webhook session")
def UpdateSessionLog(self, status, message):
self.webHookSessionLogs.addLog(status, message)
with self.engine.begin() as conn:
conn.execute(
self.webHookSessionsTable.update().values({
"Logs": self.webHookSessionLogs.model_dump()
}).where(
self.webHookSessionsTable.c.WebHookSessionID == self.sessionID
)
)
def UpdateStatus(self, status: int):
with self.engine.begin() as conn:
conn.execute(
self.webHookSessionsTable.update().values({
"Status": status,
"EndDate": datetime.now()
}).where(
self.webHookSessionsTable.c.WebHookSessionID == self.sessionID
)
)
def Execute(self):
success = False
for i in range(5):
headerDictionary = {
'Content-Type': self.webHook.ContentType
}
for header in self.webHook.Headers.values():
if header['key'] not in ['Content-Type']:
headerDictionary[header['key']] = header['value']
if self.webHook.ContentType == "application/json":
reqData = json.dumps(self.data)
else:
for (key, val) in self.data.items():
if type(self.data[key]) not in [str, int]:
self.data[key] = json.dumps(self.data[key])
reqData = urllib.parse.urlencode(self.data)
try:
req = requests.post(
self.webHook.PayloadURL, headers=headerDictionary, timeout=10, data=reqData, verify=self.webHook.VerifySSL
)
req.raise_for_status()
success = True
self.UpdateSessionLog(0, "Webhook request finished")
self.UpdateSessionLog(0, json.dumps({"returned_data": req.text}))
self.UpdateStatus(0)
break
except requests.exceptions.RequestException as e:
self.UpdateSessionLog(1, f"Attempt #{i + 1}/5. Request errored. Reason: " + str(e))
time.sleep(10)
if not success:
self.UpdateSessionLog(1, "Webhook request failed & terminated.")
self.UpdateStatus(1)

76
src/modules/Email.py Normal file
View File

@ -0,0 +1,76 @@
import os.path
import smtplib
from email import encoders
from email.header import Header
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formataddr
class EmailSender:
def __init__(self, DashboardConfig):
self.smtp = None
self.DashboardConfig = DashboardConfig
if not os.path.exists('./attachments'):
os.mkdir('./attachments')
def Server(self):
return self.DashboardConfig.GetConfig("Email", "server")[1]
def Port(self):
return self.DashboardConfig.GetConfig("Email", "port")[1]
def Encryption(self):
return self.DashboardConfig.GetConfig("Email", "encryption")[1]
def Username(self):
return self.DashboardConfig.GetConfig("Email", "username")[1]
def Password(self):
return self.DashboardConfig.GetConfig("Email", "email_password")[1]
def SendFrom(self):
return self.DashboardConfig.GetConfig("Email", "send_from")[1]
# Thank you, @gdeeble from GitHub
def AuthenticationRequired(self):
return self.DashboardConfig.GetConfig("Email", "authentication_required")[1]
def ready(self):
if self.AuthenticationRequired():
return all([self.Server(), self.Port(), self.Encryption(), self.Username(), self.Password(), self.SendFrom()])
return all([self.Server(), self.Port(), self.Encryption(), self.SendFrom()])
def send(self, receiver, subject, body, includeAttachment = False, attachmentName = "") -> tuple[bool, str] | tuple[bool, None]:
if self.ready():
try:
self.smtp = smtplib.SMTP(self.Server(), port=int(self.Port()))
self.smtp.ehlo()
if self.Encryption() == "STARTTLS":
self.smtp.starttls()
if self.AuthenticationRequired():
self.smtp.login(self.Username(), self.Password())
message = MIMEMultipart()
message['Subject'] = subject
message['From'] = self.SendFrom()
message["To"] = receiver
message.attach(MIMEText(body, "plain"))
if includeAttachment and len(attachmentName) > 0:
attachmentPath = os.path.join('./attachments', attachmentName)
if os.path.exists(attachmentPath):
attachment = MIMEBase("application", "octet-stream")
with open(os.path.join('./attachments', attachmentName), 'rb') as f:
attachment.set_payload(f.read())
encoders.encode_base64(attachment)
attachment.add_header("Content-Disposition", f"attachment; filename= {attachmentName}",)
message.attach(attachment)
else:
self.smtp.close()
return False, "Attachment does not exist"
self.smtp.sendmail(self.SendFrom(), receiver, message.as_string())
self.smtp.close()
return True, None
except Exception as e:
return False, f"Send failed | Reason: {e}"
return False, "SMTP not configured"

22
src/modules/Log.py Normal file
View File

@ -0,0 +1,22 @@
"""
Log Class
"""
class Log:
def __init__(self, LogID: str, JobID: str, LogDate: str, Status: str, Message: str):
self.LogID = LogID
self.JobID = JobID
self.LogDate = LogDate
self.Status = Status
self.Message = Message
def toJson(self):
return {
"LogID": self.LogID,
"JobID": self.JobID,
"LogDate": self.LogDate,
"Status": self.Status,
"Message": self.Message
}
def __dict__(self):
return self.toJson()

View File

@ -0,0 +1,88 @@
import uuid
from pydantic import BaseModel, field_serializer
import sqlalchemy as db
from .ConnectionString import ConnectionString
class NewConfigurationTemplate(BaseModel):
TemplateID: str = ''
Subnet: str = ''
ListenPortStart: int = 0
ListenPortEnd: int = 0
Notes: str = ""
class NewConfigurationTemplates:
def __init__(self):
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.templatesTable = db.Table(
'NewConfigurationTemplates', self.metadata,
db.Column('TemplateID', db.String(255), primary_key=True),
db.Column('Subnet', db.String(255)),
db.Column('ListenPortStart', db.Integer),
db.Column('ListenPortEnd', db.Integer),
db.Column('Notes', db.Text),
)
self.metadata.create_all(self.engine)
self.Templates: list[NewConfigurationTemplate] = []
self.__getTemplates()
def GetTemplates(self):
self.__getTemplates()
return list(map(lambda x : x.model_dump(), self.Templates))
def __getTemplates(self):
with self.engine.connect() as conn:
templates = conn.execute(
self.templatesTable.select()
).mappings().fetchall()
self.Templates.clear()
self.Templates = [NewConfigurationTemplate(**template) for template in templates]
def CreateTemplate(self) -> NewConfigurationTemplate:
return NewConfigurationTemplate(TemplateID=str(uuid.uuid4()))
def SearchTemplate(self, template: NewConfigurationTemplate):
try:
first = next(filter(lambda x : x.TemplateID == template.TemplateID, self.Templates))
except StopIteration:
return None
return first
def UpdateTemplate(self, template: dict[str, str]) -> tuple[bool, str] | tuple[bool, None]:
try:
template = NewConfigurationTemplate(**template)
with self.engine.begin() as conn:
if self.SearchTemplate(template):
conn.execute(
self.templatesTable.update().values(
template.model_dump(exclude={'TemplateID'})
).where(
self.templatesTable.c.TemplateID == template.TemplateID
)
)
else:
conn.execute(
self.templatesTable.insert().values(
template.model_dump()
)
)
self.__getTemplates()
except Exception as e:
return False, str(e)
return True, None
def DeleteTemplate(self, template: dict[str, str]) -> tuple[bool, str] | tuple[bool, None]:
try:
template = NewConfigurationTemplate(**template)
with self.engine.begin() as conn:
conn.execute(
self.templatesTable.delete().where(
self.templatesTable.c.TemplateID == template.TemplateID
)
)
self.__getTemplates()
except Exception as e:
return False, str(e)
return True, None

354
src/modules/Peer.py Normal file
View File

@ -0,0 +1,354 @@
"""
Peer
"""
import base64
import datetime
import json
import os, subprocess, uuid, random, re
from datetime import timedelta
import jinja2
import sqlalchemy as db
from .PeerJob import PeerJob
from .PeerShareLink import PeerShareLink
from .Utilities import GenerateWireguardPublicKey, ValidateIPAddressesWithRange, ValidateDNSAddress
class Peer:
def __init__(self, tableData, configuration):
self.configuration = configuration
self.id = tableData["id"]
self.private_key = tableData["private_key"]
self.DNS = tableData["DNS"]
self.endpoint_allowed_ip = tableData["endpoint_allowed_ip"]
self.name = tableData["name"]
self.total_receive = tableData["total_receive"]
self.total_sent = tableData["total_sent"]
self.total_data = tableData["total_data"]
self.endpoint = tableData["endpoint"]
self.status = tableData["status"]
self.latest_handshake = tableData["latest_handshake"]
self.allowed_ip = tableData["allowed_ip"]
self.cumu_receive = tableData["cumu_receive"]
self.cumu_sent = tableData["cumu_sent"]
self.cumu_data = tableData["cumu_data"]
self.mtu = tableData["mtu"]
self.keepalive = tableData["keepalive"]
self.remote_endpoint = tableData["remote_endpoint"]
self.preshared_key = tableData["preshared_key"]
self.jobs: list[PeerJob] = []
self.ShareLink: list[PeerShareLink] = []
self.getJobs()
self.getShareLink()
def toJson(self):
# self.getJobs()
# self.getShareLink()
return self.__dict__
def __repr__(self):
return str(self.toJson())
def updatePeer(self, name: str, private_key: str,
preshared_key: str,
dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int,
keepalive: int) -> tuple[bool, str] or tuple[bool, None]:
if not self.configuration.getStatus():
self.configuration.toggleConfiguration()
existingAllowedIps = [item for row in list(
map(lambda x: [q.strip() for q in x.split(',')],
map(lambda y: y.allowed_ip,
list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row]
if allowed_ip in existingAllowedIps:
return False, "Allowed IP already taken by another peer"
if not ValidateIPAddressesWithRange(endpoint_allowed_ip):
return False, f"Endpoint Allowed IPs format is incorrect"
if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses):
return False, f"DNS format is incorrect"
if type(mtu) is str or mtu is None:
mtu = 0
if mtu < 0 or mtu > 1460:
return False, "MTU format is not correct"
if type(keepalive) is str or keepalive is None:
keepalive = 0
if keepalive < 0:
return False, "Persistent Keepalive format is not correct"
if len(private_key) > 0:
pubKey = GenerateWireguardPublicKey(private_key)
if not pubKey[0] or pubKey[1] != self.id:
return False, "Private key does not match with the public key"
try:
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
pskExist = len(preshared_key) > 0
if pskExist:
with open(uid, "w+") as f:
f.write(preshared_key)
newAllowedIPs = allowed_ip.replace(" ", "")
updateAllowedIp = subprocess.check_output(
f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}",
shell=True, stderr=subprocess.STDOUT)
if pskExist: os.remove(uid)
if len(updateAllowedIp.decode().strip("\n")) != 0:
return False, "Update peer failed when updating Allowed IPs"
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
shell=True, stderr=subprocess.STDOUT)
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
return False, "Update peer failed when saving the configuration"
with self.configuration.engine.begin() as conn:
conn.execute(
self.configuration.peersTable.update().values({
"name": name,
"private_key": private_key,
"DNS": dns_addresses,
"endpoint_allowed_ip": endpoint_allowed_ip,
"mtu": mtu,
"keepalive": keepalive,
"preshared_key": preshared_key
}).where(
self.configuration.peersTable.c.id == self.id
)
)
return True, None
except subprocess.CalledProcessError as exc:
return False, exc.output.decode("UTF-8").strip()
def downloadPeer(self) -> dict[str, str]:
final = {
"fileName": "",
"file": ""
}
filename = self.name
if len(filename) == 0:
filename = "UntitledPeer"
filename = "".join(filename.split(' '))
filename = f"{filename}"
illegal_filename = [".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2", "com3",
"com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"]
for i in illegal_filename:
filename = filename.replace(i, "")
for i in filename:
if re.match("^[a-zA-Z0-9_=+.-]$", i):
final["fileName"] += i
interfaceSection = {
"PrivateKey": self.private_key,
"Address": self.allowed_ip,
"MTU": (
self.configuration.configurationInfo.OverridePeerSettings.MTU
if self.configuration.configurationInfo.OverridePeerSettings.MTU else self.mtu
),
"DNS": (
self.configuration.configurationInfo.OverridePeerSettings.DNS
if self.configuration.configurationInfo.OverridePeerSettings.DNS else self.DNS
)
}
if self.configuration.Protocol == "awg":
interfaceSection.update({
"Jc": self.configuration.Jc,
"Jmin": self.configuration.Jmin,
"Jmax": self.configuration.Jmax,
"S1": self.configuration.S1,
"S2": self.configuration.S2,
"H1": self.configuration.H1,
"H2": self.configuration.H2,
"H3": self.configuration.H3,
"H4": self.configuration.H4
})
peerSection = {
"PublicKey": self.configuration.PublicKey,
"AllowedIPs": (
self.configuration.configurationInfo.OverridePeerSettings.EndpointAllowedIPs
if self.configuration.configurationInfo.OverridePeerSettings.EndpointAllowedIPs else self.endpoint_allowed_ip
),
"Endpoint": f'{(self.configuration.configurationInfo.OverridePeerSettings.PeerRemoteEndpoint if self.configuration.configurationInfo.OverridePeerSettings.PeerRemoteEndpoint else self.configuration.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1])}:{(self.configuration.configurationInfo.OverridePeerSettings.ListenPort if self.configuration.configurationInfo.OverridePeerSettings.ListenPort else self.configuration.ListenPort)}',
"PersistentKeepalive": (
self.configuration.configurationInfo.OverridePeerSettings.PersistentKeepalive
if self.configuration.configurationInfo.OverridePeerSettings.PersistentKeepalive
else self.keepalive
),
"PresharedKey": self.preshared_key
}
combine = [interfaceSection.items(), peerSection.items()]
for s in range(len(combine)):
if s == 0:
final["file"] += "[Interface]\n"
else:
final["file"] += "\n[Peer]\n"
for (key, val) in combine[s]:
if val is not None and ((type(val) is str and len(val) > 0) or (type(val) is int and val > 0)):
final["file"] += f"{key} = {val}\n"
final["file"] = jinja2.Template(final["file"]).render(configuration=self.configuration)
if self.configuration.Protocol == "awg":
final["amneziaVPN"] = json.dumps({
"containers": [{
"awg": {
"isThirdPartyConfig": True,
"last_config": final['file'],
"port": self.configuration.ListenPort,
"transport_proto": "udp"
},
"container": "amnezia-awg"
}],
"defaultContainer": "amnezia-awg",
"description": self.name,
"hostName": (
self.configuration.configurationInfo.OverridePeerSettings.PeerRemoteEndpoint
if self.configuration.configurationInfo.OverridePeerSettings.PeerRemoteEndpoint
else self.configuration.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1])
})
return final
def getJobs(self):
self.jobs = self.configuration.AllPeerJobs.searchJob(self.configuration.Name, self.id)
def getShareLink(self):
self.ShareLink = self.configuration.AllPeerShareLinks.getLink(self.configuration.Name, self.id)
def resetDataUsage(self, mode: str):
try:
with self.configuration.engine.begin() as conn:
if mode == "total":
conn.execute(
self.configuration.peersTable.update().values({
"total_data": 0,
"cumu_data": 0,
"total_receive": 0,
"cumu_receive": 0,
"total_sent": 0,
"cumu_sent": 0
}).where(
self.configuration.peersTable.c.id == self.id
)
)
self.total_data = 0
self.total_receive = 0
self.total_sent = 0
self.cumu_data = 0
self.cumu_sent = 0
self.cumu_receive = 0
elif mode == "receive":
conn.execute(
self.configuration.peersTable.update().values({
"total_receive": 0,
"cumu_receive": 0,
}).where(
self.configuration.peersTable.c.id == self.id
)
)
self.cumu_receive = 0
self.total_receive = 0
elif mode == "sent":
conn.execute(
self.configuration.peersTable.update().values({
"total_sent": 0,
"cumu_sent": 0
}).where(
self.configuration.peersTable.c.id == self.id
)
)
self.cumu_sent = 0
self.total_sent = 0
else:
return False
except Exception as e:
print(e)
return False
return True
def getEndpoints(self):
result = []
with self.configuration.engine.connect() as conn:
result = conn.execute(
db.select(
self.configuration.peersHistoryEndpointTable.c.endpoint
).group_by(
self.configuration.peersHistoryEndpointTable.c.endpoint
).where(
self.configuration.peersHistoryEndpointTable.c.id == self.id
)
).mappings().fetchall()
return list(result)
def getTraffics(self, interval: int = 30, startDate: datetime.datetime = None, endDate: datetime.datetime = None):
if startDate is None and endDate is None:
endDate = datetime.datetime.now()
startDate = endDate - timedelta(minutes=interval)
else:
endDate = endDate.replace(hour=23, minute=59, second=59, microsecond=999999)
startDate = startDate.replace(hour=0, minute=0, second=0, microsecond=0)
with self.configuration.engine.connect() as conn:
result = conn.execute(
db.select(
self.configuration.peersTransferTable.c.cumu_data,
self.configuration.peersTransferTable.c.total_data,
self.configuration.peersTransferTable.c.cumu_receive,
self.configuration.peersTransferTable.c.total_receive,
self.configuration.peersTransferTable.c.cumu_sent,
self.configuration.peersTransferTable.c.total_sent,
self.configuration.peersTransferTable.c.time
).where(
db.and_(
self.configuration.peersTransferTable.c.id == self.id,
self.configuration.peersTransferTable.c.time <= endDate,
self.configuration.peersTransferTable.c.time >= startDate,
)
).order_by(
self.configuration.peersTransferTable.c.time
)
).mappings().fetchall()
return list(result)
def getSessions(self, startDate: datetime.datetime = None, endDate: datetime.datetime = None):
if endDate is None:
endDate = datetime.datetime.now()
if startDate is None:
startDate = endDate
endDate = endDate.replace(hour=23, minute=59, second=59, microsecond=999999)
startDate = startDate.replace(hour=0, minute=0, second=0, microsecond=0)
with self.configuration.engine.connect() as conn:
result = conn.execute(
db.select(
self.configuration.peersTransferTable.c.time
).where(
db.and_(
self.configuration.peersTransferTable.c.id == self.id,
self.configuration.peersTransferTable.c.time <= endDate,
self.configuration.peersTransferTable.c.time >= startDate,
)
).order_by(
self.configuration.peersTransferTable.c.time
)
).fetchall()
time = list(map(lambda x : x[0], result))
return time
def __duration(self, t1: datetime.datetime, t2: datetime.datetime):
delta = t1 - t2
hours, remainder = divmod(delta.total_seconds(), 3600)
minutes, seconds = divmod(remainder, 60)
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"

32
src/modules/PeerJob.py Normal file
View File

@ -0,0 +1,32 @@
"""
Peer Job
"""
from datetime import datetime
class PeerJob:
def __init__(self, JobID: str, Configuration: str, Peer: str,
Field: str, Operator: str, Value: str, CreationDate: datetime, ExpireDate: datetime, Action: str):
self.Action = Action
self.ExpireDate = ExpireDate
self.CreationDate = CreationDate
self.Value = Value
self.Operator = Operator
self.Field = Field
self.Configuration = Configuration
self.Peer = Peer
self.JobID = JobID
def toJson(self):
return {
"JobID": self.JobID,
"Configuration": self.Configuration,
"Peer": self.Peer,
"Field": self.Field,
"Operator": self.Operator,
"Value": self.Value,
"CreationDate": self.CreationDate.strftime("%Y-%m-%d %H:%M:%S"),
"ExpireDate": (self.ExpireDate.strftime("%Y-%m-%d %H:%M:%S") if self.ExpireDate is not None else None),
"Action": self.Action
}
def __dict__(self):
return self.toJson()

View File

@ -0,0 +1,101 @@
"""
Peer Job Logger
"""
import uuid
from typing import Sequence
import sqlalchemy as db
from flask import current_app
from sqlalchemy import RowMapping
from .ConnectionString import ConnectionString
from .Log import Log
class PeerJobLogger:
def __init__(self, AllPeerJobs, DashboardConfig):
self.engine = db.create_engine(ConnectionString("wgdashboard_log"))
self.metadata = db.MetaData()
self.jobLogTable = db.Table('JobLog', self.metadata,
db.Column('LogID', db.String(255), nullable=False, primary_key=True),
db.Column('JobID', db.String(255), nullable=False),
db.Column('LogDate', (db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('Status', db.String(255), nullable=False),
db.Column('Message', db.Text)
)
self.logs: list[Log] = []
self.metadata.create_all(self.engine)
self.AllPeerJobs = AllPeerJobs
def log(self, JobID: str, Status: bool = True, Message: str = "") -> bool:
try:
with self.engine.begin() as conn:
conn.execute(
self.jobLogTable.insert().values(
{
"LogID": str(uuid.uuid4()),
"JobID": JobID,
"Status": Status,
"Message": Message
}
)
)
except Exception as e:
current_app.logger.error(f"Peer Job Log Error", e)
return False
return True
def getLogs(self, configName = None) -> list[Log]:
logs: list[Log] = []
try:
allJobs = self.AllPeerJobs.getAllJobs(configName)
allJobsID = [x.JobID for x in allJobs]
stmt = self.jobLogTable.select().where(self.jobLogTable.columns.JobID.in_(
allJobsID
))
with self.engine.connect() as conn:
table = conn.execute(stmt).fetchall()
for l in table:
logs.append(
Log(l.LogID, l.JobID, l.LogDate.strftime("%Y-%m-%d %H:%M:%S"), l.Status, l.Message))
except Exception as e:
current_app.logger.error(f"Getting Peer Job Log Error", e)
return logs
return logs
def getFailingJobs(self) -> Sequence[RowMapping]:
with self.engine.connect() as conn:
table = conn.execute(
db.select(
self.jobLogTable.c.JobID
).where(
(db.or_(
self.jobLogTable.c.Status == 'false',
self.jobLogTable.c.Status == 0
) if conn.dialect.name == 'sqlite' else self.jobLogTable.c.Status == 'false')
).group_by(
self.jobLogTable.c.JobID
).having(
db.func.count(
self.jobLogTable.c.JobID
) > 10
)
).mappings().fetchall()
return table
def deleteLogs(self, LogID = None, JobID = None):
with self.engine.begin() as conn:
print(f"[WGDashboard] Deleted stale logs of JobID: {JobID}")
conn.execute(
self.jobLogTable.delete().where(
db.and_(
(self.jobLogTable.c.LogID == LogID if LogID is not None else True),
(self.jobLogTable.c.JobID == JobID if JobID is not None else True),
)
)
)
def vacuum(self):
with self.engine.begin() as conn:
if conn.dialect.name == 'sqlite':
print("[WGDashboard] SQLite Vacuuming PeerJobLogs Database")
conn.execute(db.text('VACUUM;'))

222
src/modules/PeerJobs.py Normal file
View File

@ -0,0 +1,222 @@
"""
Peer Jobs
"""
import sqlalchemy
from .ConnectionString import ConnectionString
from .PeerJob import PeerJob
from .PeerJobLogger import PeerJobLogger
import sqlalchemy as db
from datetime import datetime
from flask import current_app
class PeerJobs:
def __init__(self, DashboardConfig, WireguardConfigurations, AllPeerShareLinks):
self.Jobs: list[PeerJob] = []
self.engine = db.create_engine(ConnectionString('wgdashboard_job'))
self.metadata = db.MetaData()
self.peerJobTable = db.Table('PeerJobs', self.metadata,
db.Column('JobID', db.String(255), nullable=False, primary_key=True),
db.Column('Configuration', db.String(255), nullable=False),
db.Column('Peer', db.String(255), nullable=False),
db.Column('Field', db.String(255), nullable=False),
db.Column('Operator', db.String(255), nullable=False),
db.Column('Value', db.String(255), nullable=False),
db.Column('CreationDate', (db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP), nullable=False),
db.Column('ExpireDate', (db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP)),
db.Column('Action', db.String(255), nullable=False),
)
self.metadata.create_all(self.engine)
self.__getJobs()
self.JobLogger: PeerJobLogger = PeerJobLogger(self, DashboardConfig)
self.WireguardConfigurations = WireguardConfigurations
self.AllPeerShareLinks = AllPeerShareLinks
self.cleanJob(init=True)
def __getJobs(self):
self.Jobs.clear()
with self.engine.connect() as conn:
jobs = conn.execute(self.peerJobTable.select().where(
self.peerJobTable.columns.ExpireDate.is_(None)
)).mappings().fetchall()
for job in jobs:
self.Jobs.append(PeerJob(
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
job['CreationDate'], job['ExpireDate'], job['Action']))
def getAllJobs(self, configuration: str = None):
if configuration is not None:
with self.engine.connect() as conn:
jobs = conn.execute(self.peerJobTable.select().where(
self.peerJobTable.columns.Configuration == configuration
)).mappings().fetchall()
j = []
for job in jobs:
j.append(PeerJob(
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
job['CreationDate'], job['ExpireDate'], job['Action']))
return j
return []
def toJson(self):
return [x.toJson() for x in self.Jobs]
def searchJob(self, Configuration: str, Peer: str):
return list(filter(lambda x: x.Configuration == Configuration and x.Peer == Peer, self.Jobs))
def searchJobById(self, JobID):
return list(filter(lambda x: x.JobID == JobID, self.Jobs))
def saveJob(self, Job: PeerJob) -> tuple[bool, list] | tuple[bool, str]:
import traceback
try:
with self.engine.begin() as conn:
currentJob = self.searchJobById(Job.JobID)
if len(currentJob) == 0:
conn.execute(
self.peerJobTable.insert().values(
{
"JobID": Job.JobID,
"Configuration": Job.Configuration,
"Peer": Job.Peer,
"Field": Job.Field,
"Operator": Job.Operator,
"Value": Job.Value,
"CreationDate": datetime.now(),
"ExpireDate": None,
"Action": Job.Action
}
)
)
self.JobLogger.log(Job.JobID, Message=f"Job is created if {Job.Field} {Job.Operator} {Job.Value} then {Job.Action}")
else:
conn.execute(
self.peerJobTable.update().values({
"Field": Job.Field,
"Operator": Job.Operator,
"Value": Job.Value,
"Action": Job.Action
}).where(self.peerJobTable.columns.JobID == Job.JobID)
)
self.JobLogger.log(Job.JobID, Message=f"Job is updated from if {currentJob[0].Field} {currentJob[0].Operator} {currentJob[0].Value} then {currentJob[0].Action}; to if {Job.Field} {Job.Operator} {Job.Value} then {Job.Action}")
self.__getJobs()
self.WireguardConfigurations.get(Job.Configuration).searchPeer(Job.Peer)[1].getJobs()
return True, list(
filter(lambda x: x.Configuration == Job.Configuration and x.Peer == Job.Peer and x.JobID == Job.JobID,
self.Jobs))
except Exception as e:
traceback.print_exc()
return False, str(e)
def deleteJob(self, Job: PeerJob) -> tuple[bool, None] | tuple[bool, str]:
try:
if len(self.searchJobById(Job.JobID)) == 0:
return False, "Job does not exist"
with self.engine.begin() as conn:
conn.execute(
self.peerJobTable.update().values(
{
"ExpireDate": datetime.now()
}
).where(self.peerJobTable.columns.JobID == Job.JobID)
)
self.JobLogger.log(Job.JobID, Message=f"Job is removed due to being deleted or finished.")
self.__getJobs()
self.WireguardConfigurations.get(Job.Configuration).searchPeer(Job.Peer)[1].getJobs()
return True, None
except Exception as e:
return False, str(e)
def updateJobConfigurationName(self, ConfigurationName: str, NewConfigurationName: str) -> tuple[bool, str] | tuple[bool, None]:
try:
with self.engine.begin() as conn:
conn.execute(
self.peerJobTable.update().values({
"Configuration": NewConfigurationName
}).where(self.peerJobTable.columns.Configuration == ConfigurationName)
)
self.__getJobs()
return True, None
except Exception as e:
return False, str(e)
def getPeerJobLogs(self, configurationName):
return self.JobLogger.getLogs(configurationName)
def runJob(self):
self.cleanJob()
needToDelete = []
self.__getJobs()
for job in self.Jobs:
c = self.WireguardConfigurations.get(job.Configuration)
if c is not None:
f, fp = c.searchPeer(job.Peer)
if f:
if job.Field in ["total_receive", "total_sent", "total_data"]:
s = job.Field.split("_")[1]
x: float = getattr(fp, f"total_{s}") + getattr(fp, f"cumu_{s}")
y: float = float(job.Value)
else:
x: datetime = datetime.now()
y: datetime = datetime.strptime(job.Value, "%Y-%m-%d %H:%M:%S")
runAction: bool = self.__runJob_Compare(x, y, job.Operator)
if runAction:
s = False
if job.Action == "restrict":
s, msg = c.restrictPeers([fp.id])
elif job.Action == "delete":
s, msg = c.deletePeers([fp.id], self, self.AllPeerShareLinks)
elif job.Action == "reset_total_data_usage":
s = fp.resetDataUsage("total")
c.restrictPeers([fp.id])
c.allowAccessPeers([fp.id])
if s is True:
self.JobLogger.log(job.JobID, s,
f"Peer {fp.id} from {c.Name} is successfully {job.Action}ed."
)
needToDelete.append(job)
else:
self.JobLogger.log(job.JobID, s,
f"Peer {fp.id} from {c.Name} failed {job.Action}ed."
)
else:
self.JobLogger.log(job.JobID, False,
f"Somehow can't find this peer {job.Peer} from {c.Name} failed {job.Action}ed."
)
else:
self.JobLogger.log(job.JobID, False,
f"Somehow can't find this peer {job.Peer} from {job.Configuration} failed {job.Action}ed."
)
for j in needToDelete:
self.deleteJob(j)
def cleanJob(self, init = False):
failingJobs = self.JobLogger.getFailingJobs()
with self.engine.begin() as conn:
for job in failingJobs:
conn.execute(
self.peerJobTable.update().values(
{
"ExpireDate": datetime.now()
}
).where(self.peerJobTable.columns.JobID == job.get('JobID'))
)
self.JobLogger.deleteLogs(JobID=job.get('JobID'))
self.JobLogger.log(job.get('JobID'), Message=f"Job is removed due to being stale.")
with self.engine.connect() as conn:
if init and conn.dialect.name == 'sqlite':
print("[WGDashboard] SQLite Vacuuming PeerJobs Database")
self.JobLogger.vacuum()
conn.execute(sqlalchemy.text('VACUUM;'))
def __runJob_Compare(self, x: float | datetime, y: float | datetime, operator: str):
if operator == "eq":
return x == y
if operator == "neq":
return x != y
if operator == "lgt":
return x > y
if operator == "lst":
return x < y

View File

@ -0,0 +1,22 @@
from datetime import datetime
"""
Peer Share Link
"""
class PeerShareLink:
def __init__(self, ShareID:str, Configuration: str, Peer: str, ExpireDate: datetime, SharedDate: datetime):
self.ShareID = ShareID
self.Peer = Peer
self.Configuration = Configuration
self.SharedDate = SharedDate
self.ExpireDate = ExpireDate
if not self.ExpireDate:
self.ExpireDate = datetime.strptime("2199-12-31","%Y-%m-%d")
def toJson(self):
return {
"ShareID": self.ShareID,
"Peer": self.Peer,
"Configuration": self.Configuration,
"ExpireDate": self.ExpireDate.strftime("%Y-%m-%d %H:%M:%S"),
"SharedDate": self.SharedDate.strftime("%Y-%m-%d %H:%M:%S"),
}

View File

@ -0,0 +1,89 @@
from .ConnectionString import ConnectionString
from .PeerShareLink import PeerShareLink
import sqlalchemy as db
from datetime import datetime
import uuid
"""
Peer Share Links
"""
class PeerShareLinks:
def __init__(self, DashboardConfig, WireguardConfigurations):
self.Links: list[PeerShareLink] = []
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.peerShareLinksTable = db.Table(
'PeerShareLinks', self.metadata,
db.Column('ShareID', db.String(255), nullable=False, primary_key=True),
db.Column('Configuration', db.String(255), nullable=False),
db.Column('Peer', db.String(255), nullable=False),
db.Column('ExpireDate', (db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP)),
db.Column('SharedDate', (db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
server_default=db.func.now()),
)
self.metadata.create_all(self.engine)
self.__getSharedLinks()
self.wireguardConfigurations = WireguardConfigurations
def __getSharedLinks(self):
self.Links.clear()
with self.engine.connect() as conn:
allLinks = conn.execute(
self.peerShareLinksTable.select().where(
db.or_(self.peerShareLinksTable.columns.ExpireDate.is_(None), self.peerShareLinksTable.columns.ExpireDate > datetime.now())
)
).mappings().fetchall()
for link in allLinks:
self.Links.append(PeerShareLink(**link))
def getLink(self, Configuration: str, Peer: str) -> list[PeerShareLink]:
self.__getSharedLinks()
return list(filter(lambda x : x.Configuration == Configuration and x.Peer == Peer, self.Links))
def getLinkByID(self, ShareID: str) -> list[PeerShareLink]:
self.__getSharedLinks()
return list(filter(lambda x : x.ShareID == ShareID, self.Links))
def addLink(self, Configuration: str, Peer: str, ExpireDate: datetime = None) -> tuple[bool, str]:
try:
newShareID = str(uuid.uuid4())
with self.engine.begin() as conn:
if len(self.getLink(Configuration, Peer)) > 0:
conn.execute(
self.peerShareLinksTable.update().values(
{
"ExpireDate": datetime.now()
}
).where(db.and_(self.peerShareLinksTable.columns.Configuration == Configuration, self.peerShareLinksTable.columns.Peer == Peer))
)
conn.execute(
self.peerShareLinksTable.insert().values(
{
"ShareID": newShareID,
"Configuration": Configuration,
"Peer": Peer,
"ExpireDate": ExpireDate
}
)
)
self.__getSharedLinks()
self.wireguardConfigurations.get(Configuration).searchPeer(Peer)[1].getShareLink()
except Exception as e:
return False, str(e)
return True, newShareID
def updateLinkExpireDate(self, ShareID, ExpireDate: datetime = None) -> tuple[bool, str]:
with self.engine.begin() as conn:
updated = conn.execute(
self.peerShareLinksTable.update().values(
{
"ExpireDate": ExpireDate
}
).returning(self.peerShareLinksTable.c.Configuration, self.peerShareLinksTable.c.Peer)
.where(self.peerShareLinksTable.columns.ShareID == ShareID)
).mappings().fetchone()
self.__getSharedLinks()
self.wireguardConfigurations.get(updated.Configuration).searchPeer(updated.Peer)[1].getShareLink()
return True, ""

204
src/modules/SystemStatus.py Normal file
View File

@ -0,0 +1,204 @@
import shutil, subprocess, time, threading, psutil
from flask import current_app
class SystemStatus:
def __init__(self):
self.CPU = CPU()
self.MemoryVirtual = Memory('virtual')
self.MemorySwap = Memory('swap')
self.Disks = Disks()
self.NetworkInterfaces = NetworkInterfaces()
self.Processes = Processes()
def toJson(self):
process = [
threading.Thread(target=self.CPU.getCPUPercent),
threading.Thread(target=self.CPU.getPerCPUPercent),
threading.Thread(target=self.NetworkInterfaces.getData)
]
for p in process:
p.start()
for p in process:
p.join()
return {
"CPU": self.CPU,
"Memory": {
"VirtualMemory": self.MemoryVirtual,
"SwapMemory": self.MemorySwap
},
"Disks": self.Disks,
"NetworkInterfaces": self.NetworkInterfaces,
"NetworkInterfacesPriority": self.NetworkInterfaces.getInterfacePriorities(),
"Processes": self.Processes
}
class CPU:
def __init__(self):
self.cpu_percent: float = 0
self.cpu_percent_per_cpu: list[float] = []
def getCPUPercent(self):
try:
self.cpu_percent = psutil.cpu_percent(interval=1)
except Exception as e:
current_app.logger.error("Get CPU Percent error", e)
def getPerCPUPercent(self):
try:
self.cpu_percent_per_cpu = psutil.cpu_percent(interval=1, percpu=True)
except Exception as e:
current_app.logger.error("Get Per CPU Percent error", e)
def toJson(self):
return self.__dict__
class Memory:
def __init__(self, memoryType: str):
self.__memoryType__ = memoryType
self.total = 0
self.available = 0
self.percent = 0
def getData(self):
try:
if self.__memoryType__ == "virtual":
memory = psutil.virtual_memory()
self.available = memory.available
else:
memory = psutil.swap_memory()
self.available = memory.free
self.total = memory.total
self.percent = memory.percent
except Exception as e:
current_app.logger.error("Get Memory percent error", e)
def toJson(self):
self.getData()
return self.__dict__
class Disks:
def __init__(self):
self.disks : list[Disk] = []
def getData(self):
try:
self.disks = list(map(lambda x : Disk(x.mountpoint), psutil.disk_partitions()))
except Exception as e:
current_app.logger.error("Get Disk percent error", e)
def toJson(self):
self.getData()
return self.disks
class Disk:
def __init__(self, mountPoint: str):
self.total = 0
self.used = 0
self.free = 0
self.percent = 0
self.mountPoint = mountPoint
def getData(self):
try:
disk = psutil.disk_usage(self.mountPoint)
self.total = disk.total
self.free = disk.free
self.used = disk.used
self.percent = disk.percent
except Exception as e:
current_app.logger.error("Get Disk percent error", e)
def toJson(self):
self.getData()
return self.__dict__
class NetworkInterfaces:
def __init__(self):
self.interfaces = {}
def getInterfacePriorities(self):
if shutil.which("ip"):
result = subprocess.check_output(["ip", "route", "show"]).decode()
priorities = {}
for line in result.splitlines():
if "metric" in line and "dev" in line:
parts = line.split()
dev = parts[parts.index("dev")+1]
metric = int(parts[parts.index("metric")+1])
if dev not in priorities:
priorities[dev] = metric
return priorities
return {}
def getData(self):
self.interfaces.clear()
try:
network = psutil.net_io_counters(pernic=True, nowrap=True)
for i in network.keys():
self.interfaces[i] = network[i]._asdict()
time.sleep(1)
network = psutil.net_io_counters(pernic=True, nowrap=True)
for i in network.keys():
self.interfaces[i]['realtime'] = {
'sent': round((network[i].bytes_sent - self.interfaces[i]['bytes_sent']) / 1024 / 1024, 4),
'recv': round((network[i].bytes_recv - self.interfaces[i]['bytes_recv']) / 1024 / 1024, 4)
}
except Exception as e:
current_app.logger.error("Get network error", e)
def toJson(self):
return self.interfaces
class Process:
def __init__(self, name, command, pid, percent):
self.name = name
self.command = command
self.pid = pid
self.percent = percent
def toJson(self):
return self.__dict__
class Processes:
def __init__(self):
self.CPU_Top_10_Processes: list[Process] = []
self.Memory_Top_10_Processes: list[Process] = []
def getData(self):
try:
processes = list(psutil.process_iter())
cpu_processes = []
memory_processes = []
for proc in processes:
try:
name = proc.name()
cmdline = " ".join(proc.cmdline())
pid = proc.pid
cpu_percent = proc.cpu_percent()
mem_percent = proc.memory_percent()
# Create Process object for CPU and memory tracking
cpu_process = Process(name, cmdline, pid, cpu_percent)
mem_process = Process(name, cmdline, pid, mem_percent)
cpu_processes.append(cpu_process)
memory_processes.append(mem_process)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
# Skip processes we cant access or that no longer exist
continue
# Sort by CPU and memory usage (descending order)
cpu_sorted = sorted(cpu_processes, key=lambda p: p.percent, reverse=True)
mem_sorted = sorted(memory_processes, key=lambda p: p.percent, reverse=True)
# Get top 20 processes for each
self.CPU_Top_10_Processes = cpu_sorted[:20]
self.Memory_Top_10_Processes = mem_sorted[:20]
except Exception as e:
current_app.logger.error("Get processes error", e)
def toJson(self):
self.getData()
return {
"cpu_top_10": self.CPU_Top_10_Processes,
"memory_top_10": self.Memory_Top_10_Processes
}

104
src/modules/Utilities.py Normal file
View File

@ -0,0 +1,104 @@
import re, ipaddress
import subprocess
def RegexMatch(regex, text) -> bool:
"""
Regex Match
@param regex: Regex patter
@param text: Text to match
@return: Boolean indicate if the text match the regex pattern
"""
pattern = re.compile(regex)
return pattern.search(text) is not None
def GetRemoteEndpoint() -> str:
"""
Using socket to determine default interface IP address. Thanks, @NOXICS
@return:
"""
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.connect(("1.1.1.1", 80)) # Connecting to a public IP
wgd_remote_endpoint = s.getsockname()[0]
return str(wgd_remote_endpoint)
def StringToBoolean(value: str):
"""
Convert string boolean to boolean
@param value: Boolean value in string came from Configuration file
@return: Boolean value
"""
return (value.strip().replace(" ", "").lower() in
("yes", "true", "t", "1", 1))
def ValidateIPAddressesWithRange(ips: str) -> bool:
s = ips.replace(" ", "").split(",")
for ip in s:
try:
ipaddress.ip_network(ip)
except ValueError as e:
return False
return True
def ValidateIPAddresses(ips) -> bool:
s = ips.replace(" ", "").split(",")
for ip in s:
try:
ipaddress.ip_address(ip)
except ValueError as e:
return False
return True
def ValidateDNSAddress(addresses) -> tuple[bool, str]:
s = addresses.replace(" ", "").split(",")
for address in s:
if not ValidateIPAddresses(address) and not RegexMatch(
r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", address):
return False, f"{address} does not appear to be an valid DNS address"
return True, ""
def ValidateEndpointAllowedIPs(IPs) -> tuple[bool, str] | tuple[bool, None]:
ips = IPs.replace(" ", "").split(",")
for ip in ips:
try:
ipaddress.ip_network(ip, strict=False)
except ValueError as e:
return False, str(e)
return True, None
def GenerateWireguardPublicKey(privateKey: str) -> tuple[bool, str] | tuple[bool, None]:
try:
publicKey = subprocess.check_output(f"wg pubkey", input=privateKey.encode(), shell=True,
stderr=subprocess.STDOUT)
return True, publicKey.decode().strip('\n')
except subprocess.CalledProcessError:
return False, None
def GenerateWireguardPrivateKey() -> tuple[bool, str] | tuple[bool, None]:
try:
publicKey = subprocess.check_output(f"wg genkey", shell=True,
stderr=subprocess.STDOUT)
return True, publicKey.decode().strip('\n')
except subprocess.CalledProcessError:
return False, None
def ValidatePasswordStrength(password: str) -> tuple[bool, str] | tuple[bool, None]:
# Rules:
# - Must be over 8 characters & numbers
# - Must contain at least 1 Uppercase & Lowercase letters
# - Must contain at least 1 Numbers (0-9)
# - Must contain at least 1 special characters from $&+,:;=?@#|'<>.-^*()%!~_-
if len(password) < 8:
return False, "Password must be 8 characters or more"
if not re.search(r'[a-z]', password):
return False, "Password must contain at least 1 lowercase character"
if not re.search(r'[A-Z]', password):
return False, "Password must contain at least 1 uppercase character"
if not re.search(r'\d', password):
return False, "Password must contain at least 1 number"
if not re.search(r'[$&+,:;=?@#|\'<>.\-^*()%!~_-]', password):
return False, "Password must contain at least 1 special character from $&+,:;=?@#|'<>.-^*()%!~_-"
return True, None

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
from pydantic import BaseModel
class OverridePeerSettingsClass(BaseModel):
DNS: str = ''
EndpointAllowedIPs: str = ''
MTU: str | int = ''
PersistentKeepalive: int | str = ''
PeerRemoteEndpoint: str = ''
ListenPort: int | str = ''
class PeerGroupsClass(BaseModel):
GroupName: str = ''
Description: str = ''
BackgroundColor: str = ''
Icon: str = ''
Peers: list[str] = []
class WireguardConfigurationInfo(BaseModel):
Description: str = ''
OverridePeerSettings: OverridePeerSettingsClass = OverridePeerSettingsClass(**{})
PeerGroups: dict[str, PeerGroupsClass] = {}
PeerTrafficTracking: bool = True
PeerHistoricalEndpointTracking: bool = True

View File

@ -1,8 +1,17 @@
bcrypt
ifcfg
psutil
pyotp
Flask
flask-cors
icmplib
gunicorn
bcrypt==5.0.0
ifcfg==0.24
psutil==7.1.3
pyotp==2.9.0
Flask==3.1.2
flask-cors==6.0.1
icmplib==3.0.4
gunicorn==23.0.0
requests==2.32.5
tcconfig==0.30.1
sqlalchemy==2.0.44
sqlalchemy_utils==0.42.0
psycopg[binary]==3.3.2
PyMySQL==1.1.2
tzlocal==5.3.1
python-jose==3.5.0
pydantic==2.12.5

27
src/static/app/build.sh Executable file
View File

@ -0,0 +1,27 @@
#!/bin/bash
echo "Running vite build..."
if vite build; then
echo "Vite build successful."
else
echo "Vite build failed. Exiting."
exit 1
fi
echo "Checking for changes to commit..."
if git diff-index --quiet HEAD --; then
if git commit -a; then
echo "Git commit successful."
else
echo "Git commit failed. Exiting."
exit 1
fi
else
echo "No changes to commit. Skipping commit."
fi
echo "Pushing changes to remote..."
if git push; then
echo "Git push successful."
else
echo "Git push failed. Exiting."
exit 1
fi

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

View File

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/static/app/dist/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WGDashboard</title>
<script type="module" crossorigin src="/static/app/dist/assets/index.js"></script>
<link rel="stylesheet" crossorigin href="/static/app/dist/assets/index.css">
</head>
<body>
<div id="app" class="w-100 vh-100"></div>
</body>
</html>

View File

@ -2,12 +2,18 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.png">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="WGDashboard">
<meta name="apple-mobile-web-app-title" content="WGDashboard">
<link rel="manifest" href="/json/manifest.json">
<link rel="icon" href="/img/Logo-2-512x512.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WGDashboard</title>
</head>
<body>
<div id="app" class="w-100 vh-100"></div>
<div id="app"></div>
<script type="module" src="./src/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,46 @@
{
"name": "app",
"version": "4.0.0",
"version": "4.3.1",
"private": true,
"type": "module",
"module": "es2022",
"scripts": {
"dev": "vite",
"build": "vite build",
"build electron": "vite build && vite build --mode electron && cd ../../../../WGDashboard-Desktop && electron-builder",
"build": "vite build --emptyOutDir",
"buildcommitpush": "./build.sh",
"build electron": "vite build --emptyOutDir && vite build --mode electron && cd ../../../../WGDashboard-Desktop && /opt/homebrew/bin/npm run \"electron dist\"",
"preview": "vite preview"
},
"dependencies": {
"@vuepic/vue-datepicker": "^9.0.1",
"@vueuse/core": "^10.9.0",
"@vueuse/shared": "^10.9.0",
"@volar/language-server": "2.4.26",
"@vue/language-server": "3.1.8",
"@vuepic/vue-datepicker": "^12.1.0",
"@vueuse/core": "^14.0.0",
"@vueuse/shared": "^14.1.0",
"animate.css": "^4.1.1",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.11.2",
"cidr-tools": "^7.0.4",
"bootstrap-icons": "^1.11.3",
"cidr-tools": "^11.0.3",
"css-color-converter": "^2.0.0",
"dayjs": "^1.11.12",
"electron-builder": "^24.13.3",
"electron-builder": "^26.0.12",
"fuse.js": "^7.0.0",
"i": "^0.3.7",
"is-cidr": "^5.0.3",
"npm": "^10.5.0",
"pinia": "^2.1.7",
"is-cidr": "^6.0.1",
"npm": "^11.6.4",
"ol": "^10.7.0",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.7.1",
"qrcode": "^1.5.3",
"qrcodejs": "^1.0.0",
"uuid": "^9.0.1",
"vue": "^3.4.29",
"simple-code-editor": "^2.0.9",
"uuid": "^13.0.0",
"vue": "^3.5.24",
"vue-chartjs": "^5.3.0",
"vue-router": "^4.2.5"
"vue-router": "^4.6.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"vite": "^5.0.10"
"@vitejs/plugin-vue": "^6.0.0",
"vite": "^7.2.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@ -0,0 +1,48 @@
{
"theme_color": "#343a40",
"background_color": "#343a40",
"display": "fullscreen",
"scope": "/",
"start_url": "/",
"name": "WGDashboard",
"short_name": "WGDashboard",
"screenshots": [
{
"src": "https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/sign-in.png",
"sizes": "2880x1826",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Documentation%20Images/index.png",
"sizes": "2880x1826",
"type": "image/png"
}
],
"icons": [
{
"src": "../img/Logo-2-Rounded-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "any"
},
{
"src": "../img/Logo-2-Rounded-256x256.png",
"sizes": "256x256",
"type": "image/png",
"purpose": "any"
},
{
"src": "../img/Logo-2-Rounded-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any"
},
{
"src": "../img/Logo-2-Rounded-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
}
]
}

View File

@ -1,72 +1,75 @@
<script setup>
import { RouterView } from 'vue-router'
<script setup async>
import {RouterView, useRoute} from 'vue-router'
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {computed, watch} from "vue";
const store = DashboardConfigurationStore();
import "@/utilities/wireguard.js"
import {fetchGet} from "@/utilities/fetch.js";
store.initCrossServerConfiguration();
if (window.IS_WGDASHBOARD_DESKTOP){
store.IsElectronApp = true;
store.CrossServerConfiguration.Enable = true;
if (store.ActiveServerConfiguration){
fetchGet("/api/locale", {}, (res) => {
store.Locale = res.data
})
}
}else{
fetchGet("/api/locale", {}, (res) => {
store.Locale = res.data
})
}
watch(store.CrossServerConfiguration, () => {
store.syncCrossServerConfiguration()
}, {
deep: true
});
const getActiveCrossServer = computed(() => {
if (store.ActiveServerConfiguration){
return store.CrossServerConfiguration.ServerList[store.ActiveServerConfiguration]
}
return undefined
})
const route = useRoute()
</script>
<template>
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark">
<div class="h-100 bg-body" :data-bs-theme="store.Configuration?.Server.dashboard_theme">
<div style="z-index: 9999; height: 5px" class="position-absolute loadingBar top-0 start-0"></div>
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark" v-if="!route.meta.hideTopNav">
<div class="container-fluid d-flex text-body align-items-center">
<span class="navbar-brand mb-0 h1">WGDashboard</span>
<small class="ms-auto text-muted" v-if="getActiveCrossServer !== undefined">
<i class="bi bi-server me-2"></i>{{getActiveCrossServer.host}}
</small>
<RouterLink to="/" class="navbar-brand mb-0 h1">
<img src="/img/Logo-2-Rounded-512x512.png" alt="WGDashboard Logo" style="width: 32px">
</RouterLink>
<a role="button" class="navbarBtn text-body"
@click="store.ShowNavBar = !store.ShowNavBar"
style="line-height: 0; font-size: 2rem">
<i class="bi bi-list"></i></a>
<Transition name="fade2" mode="out-in">
<i class="bi bi-list" v-if="!store.ShowNavBar"></i>
<i class="bi bi-x-lg" v-else></i>
</Transition>
</a>
</div>
</nav>
<Suspense>
<RouterView v-slot="{ Component }">
<Transition name="app" mode="out-in">
<Transition name="app" mode="out-in" type="transition" appear>
<Component :is="Component"></Component>
</Transition>
</RouterView>
</Suspense>
</div>
</template>
<style scoped>
.app-enter-active,
.app-leave-active {
transition: all 0.3s cubic-bezier(0.82, 0.58, 0.17, 0.9);
transition: all 0.7s cubic-bezier(0.82, 0.58, 0.17, 1);
}
.app-enter-from{
transform: translateY(20px);
opacity: 0;
}
.app-enter-from,
.app-leave-to{
transform: translateY(-20px);
opacity: 0;
transform: scale(1.05);
filter: blur(8px);
}
@media screen and (min-width: 768px) {
.navbarBtn{
.navbar{
display: none;
}
}
</style>

View File

@ -0,0 +1,96 @@
<script setup lang="ts">
import {computed, ref} from "vue";
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
import LocaleText from "@/components/text/localeText.vue";
const props = defineProps(['configuration', 'peers', 'clientAssignedPeers', 'availablePeerSearchString'])
const emits = defineEmits(['assign', 'unassign'])
const assignmentStore = DashboardClientAssignmentStore()
const available = computed(() => {
if (props.clientAssignedPeers){
if (Object.keys(props.clientAssignedPeers).includes(props.configuration)){
return props.peers.filter(
x => {
return !props.clientAssignedPeers[props.configuration].map(
x => x.id
).includes(x.id) &&
(!props.availablePeerSearchString ||
(props.availablePeerSearchString &&
(x.id.includes(props.availablePeerSearchString) || x.name.includes(props.availablePeerSearchString))))
}
)
}
}
return props.availablePeerSearchString ? props.peers.filter(
x => x.id.includes(props.availablePeerSearchString) || x.name.includes(props.availablePeerSearchString)
) : props.peers
})
const confirmDelete = ref(false)
const collapse = ref(false)
</script>
<template>
<div class="card rounded-0 border-0">
<div
@click="collapse = !collapse"
role="button"
class="card-header rounded-0 sticky-top bg-body-secondary border-0 border-bottom text-white d-flex">
<small><samp>{{ configuration }}</samp></small>
<a role="button" class="ms-auto text-white" >
<i class="bi bi-chevron-compact-down" v-if="collapse"></i>
<i class="bi bi-chevron-compact-up" v-else></i>
</a>
</div>
<div class="card-body p-0" v-if="!collapse">
<div class="list-group list-group-flush" >
<div
class="list-group-item d-flex border-bottom list-group-item-action d-flex align-items-center gap-3"
:key="peer.id"
v-for="peer in available" >
<div v-if="!confirmDelete">
<small class="text-body">
<RouterLink
class="text-decoration-none"
target="_blank"
:to="'/configuration/' + configuration +'/peers?id=' + encodeURIComponent(peer.id)">
<samp>{{ peer.id }}</samp>
</RouterLink>
</small><br>
<small class="text-muted">
{{ peer.name ? peer.name : 'Untitled Peer'}}
</small>
</div>
<div v-else>
<small class="text-body">
<LocaleText t="Are you sure to remove this peer?"></LocaleText>
</small><br>
<small class="text-muted">
<samp>{{ peer.id }}</samp>
</small>
</div>
<template v-if="clientAssignedPeers">
<button
@click="emits('assign', peer.id)"
:class="{disabled: assignmentStore.assigning}"
class="btn bg-success-subtle text-success-emphasis ms-auto">
<i class="bi bi-plus-circle-fill" ></i>
</button>
</template>
<button
v-else
@click="emits('unassign', peer.assignment_id)"
:class="{disabled: assignmentStore.unassigning}"
aria-label="Delete Assignment"
class="btn bg-danger-subtle text-danger-emphasis ms-auto">
<i class="bi bi-trash-fill"></i>
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,109 @@
<script setup lang="ts" async>
import {onMounted, ref, watch, watchEffect} from "vue";
import { fetchGet } from "@/utilities/fetch.js"
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
import AvailablePeersGroup from "@/components/clientComponents/availablePeersGroup.vue";
import LocaleText from "@/components/text/localeText.vue";
const props = defineProps(['client', 'clientAssignedPeers'])
const loading = ref(false)
const assignmentStore = DashboardClientAssignmentStore()
const manage = ref(false)
const emits = defineEmits(['refresh'])
const assign = async (ConfigurationName, Peer, ClientID) => {
await assignmentStore.assignClient(ConfigurationName, Peer, ClientID, false)
emits('refresh')
}
const unassign = async (AssignmentID) => {
await assignmentStore.unassignClient(undefined, undefined, AssignmentID)
emits('refresh')
}
const availablePeerSearchString = ref("")
</script>
<template>
<div>
<div class="d-flex rounded-0 border-0 flex-column d-flex flex-column border-bottom pb-1" v-if="!loading">
<div class="d-flex flex-column p-3 gap-3">
<div class="d-flex align-items-center">
<h6 class="mb-0">
<LocaleText t="Assigned Peers"></LocaleText>
<span class="text-bg-primary badge ms-2">
{{ Object.keys(clientAssignedPeers).length }} <LocaleText :t="Object.keys(clientAssignedPeers).length > 1 ? 'Configurations' : 'Configuration'"></LocaleText>
</span>
<span class="text-bg-info badge ms-2">
{{ Object.values(clientAssignedPeers).flat().length }} <LocaleText :t="Object.values(clientAssignedPeers).flat().length > 1 ? 'Peers' : 'Peer'"></LocaleText>
</span>
</h6>
<button class="btn btn-sm bg-primary-subtle text-primary-emphasis rounded-3 ms-auto"
@click="manage = !manage">
<template v-if="!manage">
<i class="bi bi-list-check me-2"></i>
<LocaleText t="Manage"></LocaleText>
</template>
<template v-else>
<i class="bi bi-check me-2"></i>
<LocaleText t="Done"></LocaleText>
</template>
</button>
</div>
<div class="rounded-3 availablePeers border h-100 overflow-scroll flex-grow-1 d-flex flex-column">
<AvailablePeersGroup
:configuration="configuration"
:peers="peers"
@unassign="async (id) => await unassign(id)"
v-for="(peers, configuration) in clientAssignedPeers">
</AvailablePeersGroup>
<h6 class="text-muted m-auto p-3" v-if="Object.keys(clientAssignedPeers).length === 0">
<LocaleText t="No peer assigned to this client"></LocaleText>
</h6>
</div>
</div>
<div style="height: 500px" class="d-flex flex-column p-3" v-if="manage">
<div class="availablePeers border h-100 card rounded-3">
<div class="card-header sticky-top p-3">
<h6 class="mb-0 d-flex align-items-center">
<LocaleText t="Available Peers"></LocaleText>
</h6>
</div>
<div class="card-body p-0 overflow-scroll">
<AvailablePeersGroup
:availablePeerSearchString="availablePeerSearchString"
:configuration="configuration"
:clientAssignedPeers="clientAssignedPeers"
:peers="peers"
:key="configuration"
@assign="async (id) => await assign(configuration, id, props.client.ClientID)"
v-for="(peers, configuration) in assignmentStore.allConfigurationsPeers">
</AvailablePeersGroup>
<h6 class="text-muted m-auto" v-if="Object.keys(assignmentStore.allConfigurationsPeers).length === 0">
<LocaleText t="No peer is available to assign"></LocaleText>
</h6>
</div>
<div class="card-footer d-flex gap-2 p-3 align-items-center justify-content-end">
<label for="availablePeerSearchString">
<i class="bi bi-search me-2"></i>
</label>
<input
id="availablePeerSearchString"
v-model="availablePeerSearchString"
class="form-control form-control-sm rounded-3 w-auto" type="text">
</div>
</div>
</div>
</div>
<div v-else>
<div class="p-3 placeholder-glow border-bottom">
<h6 class="placeholder w-100 rounded-3"></h6>
<div class="placeholder w-100 rounded-3" style="height: 400px"></div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,62 @@
<script setup lang="ts">
import LocaleText from "@/components/text/localeText.vue";
import { fetchPost } from "@/utilities/fetch"
import {ref} from "vue";
import { DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore.js"
const props = defineProps(['client'])
const deleting = ref(false)
const confirmDelete = ref(false)
const emits = defineEmits(['refresh'])
const dashboardConfigurationStore = DashboardConfigurationStore()
const deleteClient = async () => {
deleting.value = true
await fetchPost("/api/clients/deleteClient", {
ClientID: props.client.ClientID
}, (res) => {
deleting.value = false
if (res.status){
emits("deleteSuccess")
dashboardConfigurationStore.newMessage("Server", "Delete client successfully", "success")
}else {
dashboardConfigurationStore.newMessage("Server", "Failed to delete client", "danger")
}
})
}
</script>
<template>
<div class="p-3 d-flex gap-3 flex-column border-bottom">
<div class="d-flex align-items-center gap-2">
<h6 class="mb-0">
<LocaleText t="Delete Client" v-if="!confirmDelete"></LocaleText>
<LocaleText t="Are you sure to delete this client?" v-else></LocaleText>
</h6>
<button class="btn btn-sm bg-danger-subtle text-danger-emphasis rounded-3 ms-auto"
v-if="!confirmDelete"
@click="confirmDelete = true"
>
<i class="bi bi-trash-fill me-2"></i>
<LocaleText t="Delete"></LocaleText>
</button>
<template v-if="confirmDelete">
<button
@click="deleteClient"
class="btn btn-sm bg-danger-subtle text-danger-emphasis rounded-3 ms-auto">
<i class="bi bi-trash-fill me-2"></i>
<LocaleText t="Yes"></LocaleText>
</button>
<button class="btn btn-sm bg-secondary-subtle text-secondary-emphasis rounded-3"
v-if="confirmDelete" @click="confirmDelete = false">
<i class="bi bi-x-lg me-2"></i>
<LocaleText t="No"></LocaleText>
</button>
</template>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,57 @@
<script setup lang="ts">
import {computed, onMounted} from "vue";
import LocaleText from "@/components/text/localeText.vue";
import {useRoute} from "vue-router";
const props = defineProps(['groupName', 'clients', 'searchString'])
const getClients = computed(() => {
const s = props.searchString.toLowerCase()
if (!props.searchString){
return props.clients
}
return props.clients.filter(
x =>
(x.ClientID && x.ClientID.toLowerCase().includes(s)) ||
(x.Email && x.Email.toLowerCase().includes(s) ||
(x.Name && x.Name.toLowerCase().includes(s)))
)
})
const route = useRoute()
onMounted(() => {
document.querySelector(".clientList .active")?.scrollIntoView()
})
</script>
<template>
<div class="card rounded-0 border-0">
<div class="card-header d-flex align-items-center rounded-0">
<h6 class="my-2">{{ groupName }}</h6>
<span class="badge text-bg-primary ms-auto">
<LocaleText :t="getClients.length + ' Client' + (getClients.length > 1 ? 's': '')"></LocaleText>
</span>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush clientList">
<RouterLink
:key="client.ClientID"
:id="'client_' + client.ClientID"
active-class="active"
:to="{ name: 'Client Viewer', params: { id: client.ClientID } }"
class="list-group-item d-flex flex-column border-bottom list-group-item-action client"
v-for="client in getClients" >
<small class="text-body">
{{ client.Email }}
</small>
<small class="text-muted">
{{ client.Name ? client.Name : 'No Name'}}
</small>
</RouterLink>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,96 @@
<script setup lang="ts">
import LocaleText from "@/components/text/localeText.vue";
import { fetchGet, fetchPost } from "@/utilities/fetch.js"
import {ref} from "vue";
import {DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore.js"
import {useRouter} from "vue-router";
const props = defineProps(['client'])
const alert = ref(false)
const alertStatus = ref(false)
const alertMessage = ref(false)
const resetting = ref(false)
const store = DashboardConfigurationStore();
const router = useRouter()
const getUrl = (token) => {
const crossServer = store.getActiveCrossServer();
if(crossServer){
return new URL('/client/#/reset_password?token=' + token, crossServer.host).href
}
return new URL('/client/#/reset_password?token=' + token, window.location.href).href
}
const sendResetLink = async () => {
resetting.value = true
let smtpReady = false;
let token = undefined;
await fetchPost('/api/clients/generatePasswordResetLink', {
ClientID: props.client.ClientID
},async (res) => {
if (res.status){
token = res.data
alertStatus.value = true
await fetchGet('/api/email/ready', {}, (res) => {
smtpReady = res.status
});
if (smtpReady){
let body = {
"Receiver": props.client.Email,
"Subject": "[WGDashboard | Client] Reset Password",
"Body":
`Hi${props.client.Name ? ' ' + props.client.Name: ''},\n\nWe received a request to reset the password for your account. You can reset your password by visiting the link below:\n\n${getUrl(token)}\n\nThis link will expire in 30 minutes for your security. If you didnt request a password reset, you can safely ignore this email—your current password will remain unchanged.\n\nIf you need help, feel free to contact support.\n\nBest regards,\nWGDashboard`
}
await fetchPost('/api/email/send', body, (res) => {
if (res.status){
alertMessage.value = `Send email success.`
alert.value = true;
}else{
alertMessage.value = `Send email failed.`
alertStatus.value = false;
alert.value = true;
}
});
}else{
alertMessage.value = `Please share this URL to your client to reset the password: ${getUrl(token)}`
alert.value = true;
}
}else{
alertStatus.value = false
alertMessage.value = res.message
alert.value = true
}
})
resetting.value = false;
}
</script>
<template>
<div class="p-3 d-flex gap-3 flex-column border-bottom">
<div class="d-flex align-items-center">
<h6 class="mb-0">
<LocaleText t="Reset Password"></LocaleText>
</h6>
<button class="btn btn-sm bg-primary-subtle text-primary-emphasis rounded-3 ms-auto"
@click="sendResetLink()"
:class="{disabled: resetting}"
>
<i class="bi bi-send me-2"></i>
<LocaleText t="Send Password Reset Link" v-if="!resetting"></LocaleText>
<LocaleText t="Sending..." v-else></LocaleText>
</button>
</div>
<div class="alert rounded-3 mb-0"
:class="[alertStatus ? 'alert-success' : 'alert-danger']"
v-if="alert">
{{ alertMessage }}
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import {ref} from "vue"
import LocaleText from "@/components/text/localeText.vue";
import { fetchGet } from "@/utilities/fetch.js"
import { DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore"
const props = defineProps(['mode'])
const dashboardConfigurationStore = DashboardConfigurationStore()
const oidcStatus = ref(false)
const oidcStatusLoading = ref(false)
const getStatus = async () => {
await fetchGet("/api/oidc/status", {
mode: props.mode
}, (res) => {
oidcStatus.value = res.data
oidcStatusLoading.value = false
})
}
await getStatus()
const toggle = async () => {
oidcStatusLoading.value = true
await fetchGet('/api/oidc/toggle', {
mode: props.mode
}, (res) => {
if (!res.status){
oidcStatus.value = !oidcStatus.value
dashboardConfigurationStore.newMessage("Server", res.message, "danger")
}
oidcStatusLoading.value = false
})
}
</script>
<template>
<div class="d-flex flex-column gap-2">
<div class="d-flex align-items-center">
<h6 class="mb-0">
<LocaleText t="OpenID Connect (OIDC)"></LocaleText>
</h6>
<div class="form-check form-switch ms-auto">
<label class="form-check-label" for="oidc_switch">
<LocaleText :t="oidcStatus ? 'Enabled':'Disabled'"></LocaleText>
</label>
<input
:disabled="oidcStatusLoading"
v-model="oidcStatus"
@change="toggle()"
class="form-check-input" type="checkbox" role="switch" id="oidc_switch">
</div>
</div>
<!-- <div>-->
<!-- <div class="alert alert-dark rounded-3 mb-0">-->
<!-- <LocaleText t="Due to security reason, in order to edit OIDC configuration, you will need to edit "></LocaleText>-->
<!-- <code>wg-dashboard-oidc-providers.json</code> <LocaleText t="directly, then restart WGDashboard to apply the latest settings."></LocaleText>-->
<!-- </div>-->
<!-- </div>-->
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,57 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import LocaleText from "@/components/text/localeText.vue";
import OidcSettings from "@/components/clientComponents/clientSettingComponents/oidcSettings.vue";
import { fetchGet } from "@/utilities/fetch.js"
const emits = defineEmits(['close'])
import { DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore"
const dashboardConfigurationStore = DashboardConfigurationStore()
const loading = ref(false)
const values = reactive({
enableClients: dashboardConfigurationStore.Configuration.Clients.enable
})
const toggling = ref(false)
const toggleClientSideApp = async () => {
toggling.value = true
await fetchGet("/api/clients/toggleStatus", {}, (res) => {
values.enableClients = res.data
})
toggling.value = false
}
</script>
<template>
<div class="position-absolute w-100 h-100 top-0 start-0 z-1 rounded-3 d-flex p-2" style="background-color: #00000070; z-index: 9999">
<div class="card m-auto rounded-3" style="width: 700px">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0">
<LocaleText t="Clients Settings"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="emits('close')"></button>
</div>
<div class="card-body px-4 d-flex gap-3 flex-column">
<div class="d-flex align-items-center">
<h6 class="mb-0">
<LocaleText t="Client Side App"></LocaleText>
</h6>
<div class="form-check form-switch ms-auto">
<label class="form-check-label" for="oidc_switch">
<LocaleText :t="values.enableClients ? 'Enabled':'Disabled'"></LocaleText>
</label>
<input
:disabled="oidcStatusLoading"
v-model="values.enableClients"
@change="toggleClientSideApp()"
class="form-check-input" type="checkbox" role="switch" id="oidc_switch">
</div>
</div>
<OidcSettings mode="Client"></OidcSettings>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,143 @@
<script setup lang="ts" async>
import {useRoute, useRouter} from "vue-router";
import { fetchGet, fetchPost } from "@/utilities/fetch.js"
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
import { DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore.js"
import {computed, reactive, ref, watch} from "vue";
import LocaleText from "@/components/text/localeText.vue";
import ClientAssignedPeers from "@/components/clientComponents/clientAssignedPeers.vue";
import ClientResetPassword from "@/components/clientComponents/clientResetPassword.vue";
import ClientDelete from "@/components/clientComponents/clientDelete.vue";
const assignmentStore = DashboardClientAssignmentStore()
const dashboardConfigurationStore = DashboardConfigurationStore()
const route = useRoute()
const router = useRouter()
const client = computed(() => {
return assignmentStore.getClientById(route.params.id)
})
const clientAssignedPeers = ref({})
const getAssignedPeers = async () => {
await fetchGet('/api/clients/assignedPeers', {
ClientID: client.value.ClientID
}, (res) => {
clientAssignedPeers.value = res.data;
})
}
const emits = defineEmits(['deleteSuccess'])
const clientProfile = reactive({
Name: undefined
})
if (client.value){
watch(() => client.value.ClientID, async () => {
clientProfile.Name = client.value.Name;
await getAssignedPeers()
})
await getAssignedPeers()
clientProfile.Name = client.value.Name
}else{
router.push('/clients')
dashboardConfigurationStore.newMessage("WGDashboard", "Client does not exist", "danger")
}
const updatingProfile = ref(false)
const updateProfile = async () => {
updatingProfile.value = true
await fetchPost("/api/clients/updateProfileName", {
ClientID: client.value.ClientID,
Name: clientProfile.Name
}, (res) => {
if (res.status){
client.value.Name = clientProfile.Name;
dashboardConfigurationStore.newMessage("Server", "Client name update success", "success")
}else{
clientProfile.Name = client.value.Name;
dashboardConfigurationStore.newMessage("Server", "Client name update failed", "danger")
}
updatingProfile.value = false
})
}
const deleteSuccess = async () => {
await router.push('/clients')
await assignmentStore.getClients()
}
</script>
<template>
<div class="text-body d-flex flex-column overflow-y-scroll h-100" v-if="client" :key="client.ClientID">
<div class="p-4 border-bottom bg-body-tertiary z-0">
<div class="mb-3 backLink">
<RouterLink to="/clients" class="text-body text-decoration-none">
<i class="bi bi-arrow-left me-2"></i>
Back</RouterLink>
</div>
<small class="text-muted">
<LocaleText t="Email"></LocaleText>
</small>
<h1>
{{ client.Email }}
</h1>
<div class="d-flex flex-column gap-2">
<div class="d-flex align-items-center">
<small class="text-muted">
<LocaleText t="Client ID"></LocaleText>
</small>
<small class="ms-auto">
<samp>{{ client.ClientID }}</samp>
</small>
</div>
<div class="d-flex align-items-center gap-2">
<small class="text-muted">
<LocaleText t="Client Name"></LocaleText>
</small>
<input class="form-control form-control-sm rounded-3 ms-auto"
style="width: 300px"
type="text" v-model="clientProfile.Name">
<button
@click="updateProfile()"
aria-label="Save Client Name"
class="btn btn-sm rounded-3 bg-success-subtle border-success-subtle text-success-emphasis">
<i class="bi bi-save-fill"></i>
</button>
</div>
</div>
</div>
<div style="flex: 1 0 0; overflow-y: scroll;">
<ClientAssignedPeers
@refresh="getAssignedPeers()"
:clientAssignedPeers="clientAssignedPeers"
:client="client"></ClientAssignedPeers>
<!-- <ClientResetPassword-->
<!-- :client="client" v-if="client.ClientGroup === 'Local'"></ClientResetPassword>-->
<ClientDelete
@deleteSuccess="deleteSuccess()"
:client="client"></ClientDelete>
</div>
</div>
<div v-else class="d-flex w-100 h-100 text-muted">
<div class="m-auto text-center">
<h1>
<i class="bi bi-person-x"></i>
</h1>
<p>
<LocaleText t="Client does not exist"></LocaleText>
</p>
</div>
</div>
</template>
<style scoped>
@media screen and (min-width: 576px) {
.backLink{
display: none;
}
}
</style>

View File

@ -0,0 +1,191 @@
<script setup>
import dayjs from "dayjs";
import {computed, ref} from "vue";
import {fetchGet, fetchPost, getUrl} from "@/utilities/fetch.js";
import {useRoute} from "vue-router";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import LocaleText from "@/components/text/localeText.vue";
const props = defineProps(["b", "delay"])
const deleteConfirmation = ref(false)
const restoreConfirmation = ref(false)
const route = useRoute()
const emit = defineEmits(["refresh", "refreshPeersList"])
const store = DashboardConfigurationStore()
const loading = ref(false);
const deleteBackup = () => {
loading.value = true;
fetchPost("/api/deleteWireguardConfigurationBackup", {
ConfigurationName: route.params.id,
BackupFileName: props.b.filename
}, (res) => {
loading.value = false;
if (res.status){
emit("refresh")
store.newMessage("Server", "Backup deleted", "success")
}else{
store.newMessage("Server", "Backup failed to delete", "danger")
}
})
}
const restoreBackup = () => {
loading.value = true;
fetchPost("/api/restoreWireguardConfigurationBackup", {
ConfigurationName: route.params.id,
BackupFileName: props.b.filename
}, (res) => {
loading.value = false;
restoreConfirmation.value = false;
if (res.status){
emit("refreshPeersList")
store.newMessage("Server", "Backup restored with " + props.b.filename, "success")
}else{
store.newMessage("Server", "Backup failed to restore", "danger")
}
})
}
const downloadBackup = () => {
fetchGet("/api/downloadWireguardConfigurationBackup", {
configurationName: route.params.id,
backupFileName: props.b.filename
}, (res) => {
if (res.status){
window.open(getUrl(`/fileDownload?file=${res.data}`), '_blank')
}
})
}
const delaySeconds = computed(() => {
return props.delay + 's'
})
const showContent = ref(false);
</script>
<template>
<div class="card my-0 rounded-3">
<div class="card-body position-relative">
<Transition name="zoomReversed">
<div
v-if="deleteConfirmation"
class="position-absolute w-100 h-100 confirmationContainer start-0 top-0 rounded-3 d-flex p-2">
<div class="m-auto">
<h5>
<LocaleText t="Are you sure to delete this backup?"></LocaleText>
</h5>
<div class="d-flex gap-2 align-items-center justify-content-center">
<button class="btn btn-danger rounded-3"
:disabled="loading"
@click='deleteBackup()'>
<LocaleText t="Yes"></LocaleText>
</button>
<button
@click="deleteConfirmation = false"
:disabled="loading"
class="btn bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3">
<LocaleText t="No"></LocaleText>
</button>
</div>
</div>
</div>
</Transition>
<Transition name="zoomReversed">
<div
v-if="restoreConfirmation"
class="position-absolute w-100 h-100 confirmationContainer start-0 top-0 rounded-3 d-flex p-2">
<div class="m-auto">
<h5>
<LocaleText t="Are you sure to restore this backup?"></LocaleText>
</h5>
<div class="d-flex gap-2 align-items-center justify-content-center">
<button
:disabled="loading"
@click="restoreBackup()"
class="btn btn-success rounded-3">
<LocaleText t="Yes"></LocaleText>
</button>
<button
@click="restoreConfirmation = false"
:disabled="loading"
class="btn bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3">
<LocaleText t="No"></LocaleText>
</button>
</div>
</div>
</div>
</Transition>
<div class="d-flex gap-3">
<div class="d-flex flex-column">
<small class="text-muted">
<LocaleText t="Backup"></LocaleText>
</small>
<samp>{{b.filename}}</samp>
</div>
<div class="d-flex flex-column">
<small class="text-muted">
<LocaleText t="Backup Date"></LocaleText>
</small>
{{dayjs(b.backupDate, "YYYYMMDDHHmmss").format("YYYY-MM-DD HH:mm:ss")}}
</div>
<div class="d-flex gap-2 align-items-center ms-auto">
<button
@click="downloadBackup()"
class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 btn-sm">
<i class="bi bi-download"></i>
</button>
<button
@click="restoreConfirmation = true"
class="btn bg-warning-subtle text-warning-emphasis border-warning-subtle rounded-3 btn-sm">
<i class="bi bi-clock-history"></i>
</button>
<button
@click="deleteConfirmation = true"
class="btn bg-danger-subtle text-danger-emphasis border-danger-subtle rounded-3 btn-sm">
<i class="bi bi-trash-fill"></i>
</button>
</div>
</div>
<hr>
<div class="card rounded-3">
<a role="button" class="card-header d-flex text-decoration-none align-items-center"
:class="{'border-bottom-0': !showContent}"
style="cursor: pointer" @click="showContent = !showContent">
<small>.conf <LocaleText t="File"></LocaleText>
</small>
<i class="bi bi-chevron-down ms-auto"></i>
</a>
<div class="card-body" v-if="showContent">
<textarea class="form-control rounded-3" :value="b.content"
disabled
style="height: 300px; font-family: var(--bs-font-monospace),sans-serif !important;"></textarea>
</div>
</div>
<hr>
<div class="d-flex">
<span>
<i class="bi bi-database me-1"></i>
<LocaleText t="Database File"></LocaleText>
</span>
<i class="bi ms-auto"
:class="[b.database ? 'text-success bi-check-circle-fill' : 'text-danger bi-x-circle-fill']"
></i>
</div>
</div>
</div>
</template>
<style scoped>
.confirmationContainer{
background-color: rgba(0, 0, 0, 0.53);
z-index: 9999;
backdrop-filter: blur(1px);
-webkit-backdrop-filter: blur(1px);
}
.list1-enter-active{
transition-delay: v-bind(delaySeconds) !important;
}
</style>

View File

@ -0,0 +1,123 @@
<script setup>
import {onBeforeUnmount, onMounted, reactive, ref} from "vue";
import {fetchGet} from "@/utilities/fetch.js";
import {useRoute} from "vue-router";
import dayjs from "dayjs";
import LocaleText from "@/components/text/localeText.vue";
import Backup from "@/components/configurationComponents/backupRestoreComponents/backup.vue";
const route = useRoute()
const backups = ref([])
const loading = ref(true)
const emit = defineEmits(["close", "refreshPeersList"])
onMounted(() => {
loadBackup();
})
const loadBackup = () => {
loading.value = true
fetchGet("/api/getWireguardConfigurationBackup", {
configurationName: route.params.id
}, (res) => {
backups.value = res.data;
loading.value = false;
})
}
const createBackup = () => {
fetchGet("/api/createWireguardConfigurationBackup", {
configurationName: route.params.id
}, (res) => {
backups.value = res.data;
loading.value = false;
})
}
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll" ref="editConfigurationContainer">
<div class="d-flex h-100 w-100">
<div class="modal-dialog-centered dashboardModal w-100 h-100 overflow-x-scroll flex-column gap-3 mx-3">
<div class="my-5 d-flex gap-3 flex-column position-relative">
<div class="title">
<div class="d-flex mb-3">
<h4 class="mb-0">
<LocaleText t="Backup & Restore"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="$emit('close')"></button>
</div>
<button
@click="createBackup()"
class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 w-100">
<i class="bi bi-plus-circle-fill me-2"></i>
<LocaleText t="Create Backup"></LocaleText>
</button>
</div>
<div class="position-relative d-flex flex-column gap-3">
<TransitionGroup name="list1" >
<div class="text-center title"
key="spinner"
v-if="loading && backups.length === 0">
<div class="spinner-border"></div>
</div>
<div class="card my-0 rounded-3"
v-else-if="!loading && backups.length === 0"
key="noBackups"
>
<div class="card-body text-center text-muted">
<i class="bi bi-x-circle-fill me-2"></i>
<LocaleText t="No backup yet, click the button above to create backup."></LocaleText>
</div>
</div>
<Backup
@refresh="loadBackup()"
@refreshPeersList="emit('refreshPeersList')"
:b="b" v-for="b in backups"
:key="b.filename"
></Backup>
</TransitionGroup>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.card, .title{
width: 100%;
}
@media screen and (min-width: 700px) {
.card, .title{
width: 700px;
}
}
.animate__fadeInUp{
animation-timing-function: cubic-bezier(0.42, 0, 0.22, 1.0)
}
.list1-move, /* apply transition to moving elements */
.list1-enter-active,
.list1-leave-active {
transition: all 0.5s cubic-bezier(0.42, 0, 0.22, 1.0);
}
.list1-enter-from,
.list1-leave-to {
opacity: 0;
transform: translateY(30px);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.list1-leave-active {
width: 100%;
position: absolute;
}
</style>

View File

@ -0,0 +1,57 @@
<script setup lang="ts">
import {ref} from "vue";
import { fetchPost } from "@/utilities/fetch.js"
const props = defineProps(['configuration'])
const description = ref(props.configuration.Info.Description)
const showStatus = ref(false)
const status = ref(false)
const updateDescription = async () => {
await fetchPost("/api/updateWireguardConfigurationInfo", {
Name: props.configuration.Name,
Key: "Description",
Value: description.value
}, (res) => {
status.value = res.status
toggleStatus()
})
}
const toggleSuccess = () => {
status.value = true
toggleStatus()
}
const toggleFail = () => {
status.value = false
toggleStatus()
}
const toggleStatus = () => {
showStatus.value = true
setTimeout(() => {
showStatus.value = false
}, 3000)
}
</script>
<template>
<div class="d-flex gap-1 flex-column">
<label for="configurationDescription">
<small style="white-space: nowrap" class="text-muted">
<i class="bi bi-pencil-fill me-2"></i>Notes
</small>
</label>
<input type="text"
:class="[showStatus ? [status ? 'is-valid':'is-invalid'] : undefined]"
id="configurationDescription"
v-model="description"
@change="updateDescription()"
class="form-control rounded-3 bg-transparent form-control-sm">
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,122 @@
<script setup>
import LocaleText from "@/components/text/localeText.vue";
import {useRoute, useRouter} from "vue-router";
import {onMounted, ref, useTemplateRef} from "vue";
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
const route = useRoute()
const configurationName = route.params.id;
const input = ref("")
const router = useRouter()
const store = DashboardConfigurationStore()
const deleting = ref(false)
const deleteConfiguration = () => {
clearInterval(store.Peers.RefreshInterval)
deleting.value = true;
fetchPost("/api/deleteWireguardConfiguration", {
ConfigurationName: configurationName
}, (res) => {
if (res.status){
router.push('/')
store.newMessage("Server", "Configuration deleted", "success")
}else{
deleting.value = false;
}
})
}
const loading = ref(true)
const backups = ref([])
const getBackup = () => {
loading.value = true;
fetchGet("/api/getWireguardConfigurationBackup", {
configurationName: configurationName
}, (res) => {
backups.value = res.data;
loading.value = false;
})
}
onMounted(() => {
getBackup()
})
const emits = defineEmits(["backup", "close"])
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
<div class="container d-flex h-100 w-100">
<div class="m-auto modal-dialog-centered dashboardModal" style="width: 700px">
<div class="card rounded-3 shadow flex-grow-1 bg-danger-subtle border-danger-subtle" id="deleteConfigurationContainer">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h5 class="mb-0">
<LocaleText t="Are you sure to delete this configuration?"></LocaleText>
</h5>
<button type="button" class="btn-close ms-auto" @click="emits('close')"></button>
</div>
<div class="card-body px-4 text-muted">
<p class="mb-0">
<LocaleText t="Once you deleted this configuration:"></LocaleText>
</p>
<ul>
<li>
<LocaleText t="All connected peers will get disconnected"></LocaleText>
</li>
<li>
<LocaleText t="Both configuration file (.conf) and database table related to this configuration will get deleted"></LocaleText>
</li>
</ul>
<div class="alert"
:class="[loading ? 'alert-secondary' : (backups.length > 0 ? 'alert-success' : 'alert-danger')]">
<div v-if="loading">
<i class="bi bi-search me-2"></i>
<LocaleText t="Checking backups..."></LocaleText>
</div>
<div v-else-if="backups.length > 0">
<i class="bi bi-check-circle-fill me-2"></i>
<LocaleText :t="'This configuration has ' + backups.length + ' backups'"></LocaleText>
</div>
<div v-else class="d-flex align-items-center gap-2">
<i class="bi bi-x-circle-fill me-2"></i>
<LocaleText t="This configuration has no backup"></LocaleText>
<a role="button"
@click="emits('backup')"
class="ms-auto btn btn-sm btn-primary rounded-3">
<i class="bi bi-clock-history me-2"></i>
<LocaleText t="Backup"></LocaleText>
</a>
<a role="button"
@click="getBackup()"
class="btn btn-sm btn-primary rounded-3">
<i class="bi bi-arrow-clockwise"></i>
</a>
</div>
</div>
<hr>
<p>
<LocaleText t="If you're sure, please type in the configuration name below and click Delete"></LocaleText>
</p>
<input class="form-control rounded-3 mb-3"
:placeholder="configurationName"
v-model="input"
type="text">
<button class="btn btn-danger w-100"
@click="deleteConfiguration()"
:disabled="input !== configurationName || deleting">
<i class="bi bi-trash-fill me-2 rounded-3"></i>
<LocaleText t="Delete" v-if="!deleting"></LocaleText>
<LocaleText t="Deleting..." v-else></LocaleText>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,270 @@
<script setup>
import LocaleText from "@/components/text/localeText.vue";
import {reactive, ref, watch} from "vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import UpdateConfigurationName
from "@/components/configurationComponents/editConfigurationComponents/updateConfigurationName.vue";
import EditRawConfigurationFile
from "@/components/configurationComponents/editConfigurationComponents/editRawConfigurationFile.vue";
import DeleteConfiguration from "@/components/configurationComponents/deleteConfiguration.vue";
import ConfigurationBackupRestore from "@/components/configurationComponents/configurationBackupRestore.vue";
import EditPeerSettingsOverride
from "@/components/configurationComponents/editConfigurationComponents/editPeerSettingsOverride.vue";
const props = defineProps({
configurationInfo: Object
})
const wgStore = WireguardConfigurationsStore()
const store = DashboardConfigurationStore()
const saving = ref(false)
const data = reactive(JSON.parse(JSON.stringify(props.configurationInfo)))
const editPrivateKey = ref(false)
const dataChanged = ref(false)
const reqField = reactive({
PrivateKey: true,
IPAddress: true,
ListenPort: true
})
const genKey = () => {
if (wgStore.checkWGKeyLength(data.PrivateKey)){
reqField.PrivateKey = true;
data.PublicKey = window.wireguard.generatePublicKey(data.PrivateKey)
}else{
reqField.PrivateKey = false;
}
}
const resetForm = () => {
dataChanged.value = false;
Object.assign(data, JSON.parse(JSON.stringify(props.configurationInfo)))
}
const emit = defineEmits(["changed", "close", "refresh", "dataChanged"])
const saveForm = () => {
saving.value = true
fetchPost("/api/updateWireguardConfiguration", data, (res) => {
saving.value = false
if (res.status){
store.newMessage("Server", "Configuration saved", "success")
dataChanged.value = false
emit("dataChanged", res.data)
}else{
store.newMessage("Server", res.message, "danger")
}
})
}
const updateConfigurationName = ref(false)
watch(data, () => {
dataChanged.value = JSON.stringify(data) !== JSON.stringify(props.configurationInfo);
}, {
deep: true
})
const editRawConfigurationFileModal = ref(false)
const backupRestoreModal = ref(false)
const deleteConfigurationModal = ref(false)
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0" ref="editConfigurationContainer">
<div class="w-100 h-100 overflow-y-scroll">
<TransitionGroup name="zoom">
<EditRawConfigurationFile
name="EditRawConfigurationFile"
v-if="editRawConfigurationFileModal"
@close="editRawConfigurationFileModal = false">
</EditRawConfigurationFile>
<DeleteConfiguration
key="DeleteConfiguration"
@backup="backupRestoreModal = true"
@close="deleteConfigurationModal = false"
v-if="deleteConfigurationModal">
</DeleteConfiguration>
<ConfigurationBackupRestore
@close="backupRestoreModal = false"
@refreshPeersList="emit('refresh')"
v-if="backupRestoreModal">
</ConfigurationBackupRestore>
</TransitionGroup>
<div class="container d-flex h-100 w-100">
<div class="m-auto modal-dialog-centered dashboardModal" style="width: 700px">
<div class="card rounded-3 shadow flex-grow-1">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
<h4 class="mb-0">
<LocaleText t="Configuration Settings"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4">
<div class="d-flex gap-2 flex-column">
<div class="d-flex align-items-center gap-3" v-if="!updateConfigurationName">
<small class="text-muted">
<LocaleText t="Name"></LocaleText>
</small>
<small>{{data.Name}}</small>
<button
@click="updateConfigurationName = true"
class="btn btn-sm bg-danger-subtle border-danger-subtle text-danger-emphasis rounded-3 ms-auto">
<LocaleText t="Update Name"></LocaleText>
</button>
</div>
<UpdateConfigurationName
@close="updateConfigurationName = false"
:configuration-name="data.Name"
v-if="updateConfigurationName"></UpdateConfigurationName>
<template v-else>
<hr>
<div class="d-flex align-items-center gap-3">
<small class="text-muted" style="word-break: keep-all">
<LocaleText t="Public Key"></LocaleText>
</small>
<small class="ms-auto" style="word-break: break-all">
{{data.PublicKey}}
</small>
</div>
<hr>
<div>
<div class="d-flex">
<label for="configuration_private_key" class="form-label">
<small class="text-muted d-block">
<LocaleText t="Private Key"></LocaleText>
</small>
</label>
<div class="form-check form-switch ms-auto">
<input class="form-check-input"
type="checkbox" role="switch" id="editPrivateKeySwitch"
v-model="editPrivateKey"
>
<label class="form-check-label" for="editPrivateKeySwitch">
<small>Edit</small>
</label>
</div>
</div>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving || !editPrivateKey"
:class="{'is-invalid': !reqField.PrivateKey}"
@keyup="genKey()"
v-model="data.PrivateKey"
id="configuration_private_key">
</div>
<div>
<label for="configuration_ipaddress_cidr" class="form-label">
<small class="text-muted">
<LocaleText t="IP Address/CIDR"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="data.Address"
id="configuration_ipaddress_cidr">
</div>
<div>
<label for="configuration_listen_port" class="form-label">
<small class="text-muted">
<LocaleText t="Listen Port"></LocaleText>
</small>
</label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="data.ListenPort"
id="configuration_listen_port">
</div>
<div class="accordion mt-2" id="editConfigurationOptionalAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed px-3 py-2" type="button" data-bs-toggle="collapse" data-bs-target="#editOptionalAccordionCollapse">
<small class="text-muted">
<LocaleText t="Optional Settings"></LocaleText>
</small>
</button>
</h2>
<div id="editOptionalAccordionCollapse"
class="accordion-collapse collapse" data-bs-parent="#editConfigurationOptionalAccordion">
<div class="accordion-body d-flex flex-column gap-3">
<div v-for="key in ['Table', 'PreUp', 'PreDown', 'PostUp', 'PostDown']">
<label :for="'configuration_' + key" class="form-label">
<small class="text-muted">
<LocaleText :t="key"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="data[key]"
:id="'configuration_' + key">
</div>
<div v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'H1', 'H2', 'H3', 'H4']"
v-if="configurationInfo.Protocol === 'awg'">
<label :for="'configuration_' + key" class="form-label">
<small class="text-muted">
<LocaleText :t="key"></LocaleText>
</small>
</label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="data[key]"
:id="'configuration_' + key">
</div>
</div>
</div>
</div>
</div>
<div class="d-flex align-items-center gap-2 mt-1">
<button class="btn btn-sm bg-secondary-subtle border-secondary-subtle text-secondary-emphasis rounded-3 shadow ms-auto"
@click="resetForm()"
:disabled="!dataChanged || saving">
<i class="bi bi-arrow-clockwise me-2"></i>
<LocaleText t="Reset"></LocaleText>
</button>
<button class="btn btn-sm bg-primary-subtle border-primary-subtle text-primary-emphasis rounded-3 shadow"
:disabled="!dataChanged || saving"
@click="saveForm()"
>
<i class="bi bi-save-fill me-2"></i>
<LocaleText t="Save"></LocaleText>
</button>
</div>
<hr>
<EditPeerSettingsOverride :configuration="configurationInfo"></EditPeerSettingsOverride>
<hr>
<h5 class="mb-3">
<LocaleText t="Danger Zone"></LocaleText>
</h5>
<div class="d-flex gap-2 flex-column">
<button
@click="backupRestoreModal = true"
class="btn bg-warning-subtle border-warning-subtle text-warning-emphasis rounded-3 text-start d-flex">
<i class="bi bi-copy me-auto"></i>
<LocaleText t="Backup & Restore"></LocaleText>
</button>
<button
@click="editRawConfigurationFileModal = true"
class="btn bg-warning-subtle border-warning-subtle text-warning-emphasis rounded-3 d-flex">
<i class="bi bi-pen me-auto"></i>
<LocaleText t="Edit Raw Configuration File"></LocaleText>
</button>
<button
@click="deleteConfigurationModal = true"
class="btn bg-danger-subtle border-danger-subtle text-danger-emphasis rounded-3 d-flex mt-4">
<i class="bi bi-trash-fill me-auto"></i>
<LocaleText t="Delete Configuration"></LocaleText>
</button>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,152 @@
<script setup lang="ts">
import LocaleText from "@/components/text/localeText.vue";
import { fetchPost } from "@/utilities/fetch.js"
import {onMounted, reactive, ref} from "vue";
const props = defineProps(['configuration'])
const saving = ref(false)
const overridePeerSettings = ref({...props.configuration.Info.OverridePeerSettings})
const edited = ref(false)
const errorMsg = ref("")
onMounted(() => {
document.querySelectorAll("#editPeerSettingsOverride input").forEach(
x => x.addEventListener("change", () => {
edited.value = true
})
)
})
const resetForm = () => {
overridePeerSettings.value = props.configuration.Info.OverridePeerSettings
edited.value = false
}
const submitForm = async () => {
document.querySelectorAll("#editPeerSettingsOverride input").forEach(
x => x.classList.remove("is-invalid", "is-valid")
)
await fetchPost("/api/updateWireguardConfigurationInfo", {
Name: props.configuration.Name,
Key: "OverridePeerSettings",
Value: overridePeerSettings.value
}, (res) => {
if (res.status){
edited.value = false
props.configuration.Info.OverridePeerSettings = overridePeerSettings.value
document.querySelectorAll("#editPeerSettingsOverride input").forEach(
x => x.classList.add("is-valid")
)
}else{
errorMsg.value = res.message
document.querySelector(`#override_${res.data}`).classList.add("is-invalid")
}
})
}
</script>
<template>
<div id="editPeerSettingsOverride">
<h5 class="mb-0">
<LocaleText t="Override Peer Settings"></LocaleText>
</h5>
<h6 class="mb-3 text-muted">
<small>
<LocaleText t="Only apply to peers in this configuration"></LocaleText>
</small>
</h6>
<div class="d-flex gap-2 flex-column">
<div>
<label for="override_DNS" class="form-label">
<small class="text-muted">
<LocaleText t="DNS"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="overridePeerSettings.DNS"
id="override_DNS">
<div class="invalid-feedback">{{ errorMsg }}</div>
</div>
<div>
<label for="override_EndpointAllowedIPs" class="form-label">
<small class="text-muted">
<LocaleText t="Endpoint Allowed IPs"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="overridePeerSettings.EndpointAllowedIPs"
id="override_EndpointAllowedIPs">
<div class="invalid-feedback">{{ errorMsg }}</div>
</div>
<div>
<label for="override_ListenPort" class="form-label">
<small class="text-muted">
<LocaleText t="Listen Port"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="overridePeerSettings.ListenPort"
id="override_ListenPort">
<div class="invalid-feedback">{{ errorMsg }}</div>
</div>
<div>
<label for="override_MTU" class="form-label">
<small class="text-muted">
<LocaleText t="MTU"></LocaleText>
</small>
</label>
<input type="text"
class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="overridePeerSettings.MTU"
id="override_MTU">
<div class="invalid-feedback">{{ errorMsg }}</div>
</div>
<div>
<label for="override_PeerRemoteEndpoint" class="form-label">
<small class="text-muted">
<LocaleText t="Peer Remote Endpoint"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="overridePeerSettings.PeerRemoteEndpoint"
id="override_PeerRemoteEndpoint">
</div>
<div>
<label for="override_persistent_keepalive" class="form-label">
<small class="text-muted">
<LocaleText t="Persistent Keepalive"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="overridePeerSettings.PersistentKeepalive"
id="override_PersistentKeepalive">
<div class="invalid-feedback">{{ errorMsg }}</div>
</div>
<div class="d-flex mt-1 gap-2">
<button
:class="{disabled: !edited}"
@click="resetForm()"
class="btn btn-sm bg-secondary-subtle border-secondary-subtle text-secondary-emphasis rounded-3 shadow ms-auto">
<i class="bi bi-arrow-clockwise me-2"></i>
<LocaleText t="Reset"></LocaleText>
</button>
<button
:class="{disabled: !edited}"
@click="submitForm()"
class="btn btn-sm bg-primary-subtle border-primary-subtle text-primary-emphasis rounded-3 shadow">
<i class="bi bi-save-fill me-2"></i>
<LocaleText t="Save"></LocaleText>
</button>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,103 @@
<script setup>
import LocaleText from "@/components/text/localeText.vue";
import CodeEditor from "@/utilities/simple-code-editor/CodeEditor.vue";
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {useRoute} from "vue-router";
import {ref} from "vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
const emits = defineEmits(['close'])
const route = useRoute()
const content = ref("")
const path = ref("")
const error = ref(false)
const errorMessage = ref("")
const getRaw = async () => {
await fetchGet('/api/getWireguardConfigurationRawFile', {
configurationName: route.params.id
}, (res) => {
content.value = res.data.content
path.value = res.data.path
})
}
await getRaw()
const dashboardStore = DashboardConfigurationStore();
const saving = ref(false)
const saveRaw = async () => {
saving.value = true
await fetchPost('/api/updateWireguardConfigurationRawFile', {
configurationName: route.params.id,
rawConfiguration: content.value
}, (res) => {
if (res.status){
error.value = false
dashboardStore.newMessage("Server", "Configuration saved", "success")
}else{
error.value = true
errorMessage.value = res.message
}
saving.value = false
})
}
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
<div class="container d-flex h-100 w-100">
<div class="m-auto modal-dialog-centered dashboardModal" style="width: 1000px">
<div class="card rounded-3 shadow flex-grow-1" id="deleteConfigurationContainer">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h5 class="mb-0">
<LocaleText t="Edit Raw Configuration File"></LocaleText>
</h5>
<button type="button" class="btn-close ms-auto" @click="emits('close')"></button>
</div>
<div class="card-body px-4 d-flex flex-column gap-3">
<div class="alert alert-danger rounded-3 mb-0" v-if="error">
<div class="mb-2">
<strong>
<LocaleText t="Failed to save configuration. Please see the following error message:"></LocaleText>
</strong>
</div>
<div class="bg-body w-100 p-2 rounded-3">
<pre>{{errorMessage}}</pre>
</div>
</div>
<CodeEditor
:disabled="true"
:read-only="saving"
v-model="content"
:theme="dashboardStore.Configuration.Server.dashboard_theme === 'dark' ? 'github-dark':'github'"
:languages="[['ini', path]]"
width="100%" height="600px">
</CodeEditor>
<div class="d-flex gap-2">
<button class="btn bg-secondary-subtle border-secondary-subtle text-secondary-emphasis rounded-3 shadow ms-auto px-3 py-2"
:disabled="saving"
@click="getRaw()">
<i class="bi bi-arrow-clockwise me-2"></i>
<LocaleText t="Reset"></LocaleText>
</button>
<button
@click="saveRaw()"
:disabled="saving"
class="btn bg-danger-subtle border-danger-subtle text-danger-emphasis rounded-3 px-3 py-2 shadow"
>
<i class="bi bi-save-fill me-2"></i>
<LocaleText t="Save" v-if="!saving"></LocaleText>
<LocaleText t="Saving..." v-else></LocaleText>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,110 @@
<script setup>
import {onMounted, reactive, ref, watch} from "vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import LocaleText from "@/components/text/localeText.vue";
import {fetchPost} from "@/utilities/fetch.js";
import {useRouter} from "vue-router";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
const props = defineProps({
configurationName: String
})
const emit = defineEmits(['close'])
const newConfigurationName = reactive({
data: "",
valid: false
});
const store = WireguardConfigurationsStore()
onMounted(() => {
watch(() => newConfigurationName.data, (newVal) => {
newConfigurationName.valid = /^[a-zA-Z0-9_=+.-]{1,15}$/.test(newVal) && newVal.length > 0 && !store.Configurations.find(x => x.Name === newVal);
})
})
const dashboardConfigurationStore = DashboardConfigurationStore()
const loading = ref(false)
const router = useRouter()
const rename = async () => {
if (newConfigurationName.data){
loading.value = true
clearInterval(dashboardConfigurationStore.Peers.RefreshInterval)
await fetchPost("/api/renameWireguardConfiguration", {
ConfigurationName: props.configurationName,
NewConfigurationName: newConfigurationName.data
}, async (res) => {
if (res.status){
await store.getConfigurations()
dashboardConfigurationStore.newMessage("Server", "Configuration renamed", "success")
router.push(`/configuration/${newConfigurationName.data}/peers`)
}else{
dashboardConfigurationStore.newMessage("Server", res.message, "danger")
loading.value = false
}
})
}
}
</script>
<template>
<div class="card rounded-3 flex-grow-1 bg-danger-subtle border-danger-subtle border shadow">
<div class="card-body">
<p>
<LocaleText t="To update this configuration's name, WGDashboard will execute the following operations:"></LocaleText>
</p>
<ol>
<li>
<LocaleText t="Duplicate current configuration's database table and .conf file with the new name"></LocaleText>
</li>
<li>
<LocaleText t="Delete current configuration's database table and .conf file"></LocaleText>
</li>
</ol>
<div class="d-flex align-items-center gap-3 inputGroup">
<input class="form-control form-control-sm rounded-3" :value="configurationName" disabled>
<h3 class="mb-0">
<i class="bi bi-arrow-right"></i>
</h3>
<input class="form-control form-control-sm rounded-3"
id="newConfigurationName"
:class="[newConfigurationName.data ? (newConfigurationName.valid ? 'is-valid' : 'is-invalid') : '']"
v-model="newConfigurationName.data">
</div>
<div class="invalid-feedback" :class="{'d-block': !newConfigurationName.valid && newConfigurationName.data}">
<LocaleText t="Configuration name is invalid. Possible reasons:"></LocaleText>
<ul class="mb-0">
<li>
<LocaleText t="Configuration name already exist"></LocaleText>
</li>
<li>
<LocaleText t="Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen."></LocaleText>
</li>
</ul>
</div>
<div class="d-flex mt-3">
<button
@click="emit('close')"
class="btn btn-sm bg-secondary-subtle border-secondary-subtle text-secondary-emphasis rounded-3">
<LocaleText t="Cancel"></LocaleText>
</button>
<button
@click="rename()"
:disabled="!newConfigurationName.data || loading"
class="btn btn-sm btn-danger rounded-3 ms-auto">
<LocaleText t="Save"></LocaleText>
</button>
</div>
</div>
</div>
</template>
<style scoped>
@media screen and (max-width: 567px) {
.inputGroup{
flex-direction: column;
h3{
transform: rotate(90deg);
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More