Compare commits

...

687 Commits

Author SHA1 Message Date
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
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
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
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
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
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
Donald Zou
16998d1e16 Merge pull request #306 from donaldzou/v4.0-fix1
Fixed peer status is not refreshing correctly
2024-08-17 20:25:34 -04:00
Donald Zou
11421d2d32 Try now 2024-08-17 20:19:52 -04:00
Donald Zou
1af708aaea Update 2024-08-17 01:11:48 -04:00
Donald Zou
fbb82fa759 Update README.md 2024-08-17 01:00:40 -04:00
Donald Zou
a9f977c3b4 Merge pull request #303 from donaldzou/v4
Merging the v4 branch to main.
2024-08-17 00:56:29 -04:00
Donald Zou
5669e22548 Removed all deprecated JS files 2024-08-17 00:52:38 -04:00
Donald Zou
da4bb9b83d Merge branch 'main' into v4 2024-08-17 00:46:46 -04:00
Donald Zou
c2cbaf0937 I think this is it :) 2024-08-17 00:31:46 -04:00
Donald Zou
d81dce536c Update README.md 2024-08-16 23:32:16 -04:00
Donald Zou
8e1e2c5b85 Update README.md 2024-08-16 23:24:20 -04:00
Donald Zou
959d5e6822 Update README.md 2024-08-16 23:22:55 -04:00
Donald Zou
036a0b4cb3 Update README.md 2024-08-16 23:18:28 -04:00
Donald Zou
c038f14374 Update README.md 2024-08-16 23:03:01 -04:00
Donald Zou
343cf91643 Update README.md 2024-08-16 23:02:36 -04:00
Donald Zou
4fe70a4c46 Update README.md 2024-08-16 23:01:58 -04:00
Donald Zou
59f7200512 Update README.md 2024-08-16 22:53:23 -04:00
Donald Zou
0335e709c2 Update README.md 2024-08-16 22:51:56 -04:00
Donald Zou
a7bb5ac21b Update wgd.sh 2024-08-16 17:36:22 -04:00
Donald Zou
99655cab33 Update wgd.sh 2024-08-16 17:35:38 -04:00
Donald Zou
348e574154 Update wgd.sh 2024-08-16 17:34:54 -04:00
Donald Zou
f35dee8643 Update wgd.sh 2024-08-16 17:34:03 -04:00
Donald Zou
168412c2e7 Update wgd.sh 2024-08-16 17:32:34 -04:00
Donald Zou
b9832542fb Update wgd.sh 2024-08-16 17:24:50 -04:00
Donald Zou
316545b253 Update wgd.sh 2024-08-16 17:23:16 -04:00
Donald Zou
348a57a0d8 Update wgd.sh 2024-08-16 17:22:57 -04:00
Donald Zou
a3f5654816 Update wgd.sh 2024-08-16 17:21:42 -04:00
Donald Zou
8207afe806 Update wgd.sh 2024-08-16 17:20:05 -04:00
Donald Zou
e15b8f8092 Update wgd.sh 2024-08-16 17:19:07 -04:00
Donald Zou
e6c99028d6 Update wgd.sh 2024-08-16 17:16:54 -04:00
Donald Zou
2e691d7c26 Update wgd.sh 2024-08-16 17:13:18 -04:00
Donald Zou
6c87812780 Update wgd.sh 2024-08-16 17:12:40 -04:00
Donald Zou
798a3632cf Update wgd.sh 2024-08-16 03:32:28 -04:00
Donald Zou
aa1d0aabd2 Update wgd.sh 2024-08-16 03:28:26 -04:00
Donald Zou
f348e691fa Update wgd.sh 2024-08-16 03:27:24 -04:00
Donald Zou
0dfda83e45 Update wgd.sh 2024-08-16 03:25:25 -04:00
Donald Zou
56044fa3f7 Update wgd.sh 2024-08-16 03:18:08 -04:00
Donald Zou
4c69fd4f60 Update wgd.sh 2024-08-16 03:14:03 -04:00
Donald Zou
92e3f7a6a3 Update wgd.sh 2024-08-16 03:12:29 -04:00
Donald Zou
76d0618d6e Update wgd.sh 2024-08-16 03:11:30 -04:00
Donald Zou
80c3a99eb1 Update wgd.sh 2024-08-16 03:10:26 -04:00
Donald Zou
91e3a3237b Update wgd.sh 2024-08-16 03:06:31 -04:00
Donald Zou
8c8c5a5826 Update wgd.sh 2024-08-16 03:02:45 -04:00
Donald Zou
b9370955d6 Update wgd.sh 2024-08-16 02:53:45 -04:00
Donald Zou
2761e728f7 Update wgd.sh 2024-08-16 02:50:54 -04:00
Donald Zou
f418bf4f63 Update wgd.sh 2024-08-16 02:48:31 -04:00
Donald Zou
3d046e4369 Update wgd.sh 2024-08-16 02:40:25 -04:00
Donald Zou
b6eb6f2d70 Update wgd.sh 2024-08-16 02:19:30 -04:00
Donald Zou
45ce3e26c1 Update wgd.sh 2024-08-16 02:15:10 -04:00
Donald Zou
ae91cedf76 Update wgd.sh 2024-08-16 02:07:40 -04:00
Donald Zou
b2a6b484b5 Update wgd.sh 2024-08-16 02:03:41 -04:00
Donald Zou
95c4fa56cc Update wgd.sh 2024-08-16 02:01:02 -04:00
Donald Zou
e8c56afa8e Update wgd.sh 2024-08-16 02:00:45 -04:00
Donald Zou
ddc3b1f6c2 Update wgd.sh 2024-08-16 01:58:40 -04:00
Donald Zou
d33d886574 Update wgd.sh 2024-08-16 01:55:53 -04:00
Donald Zou
0487980d2f Update wgd.sh
Selectively use a specific version of Python: 3.10, 3.11 or 3.12
2024-08-16 01:47:44 -04:00
Donald Zou
e68257d6c9 Updated some documentations 2024-08-15 23:20:29 -04:00
Donald Zou
ea0bbab680 Update api-documents.md 2024-08-15 22:50:01 -04:00
Donald Zou
5d4a8136c5 Updated some documentation 2024-08-15 22:47:44 -04:00
Donald Zou
c98d851cd2 No longer reading configuration file every few seconds
Only when the file is changed
2024-08-15 18:26:20 -04:00
Donald Zou
42fa89db7a Added to parse name in .conf file 2024-08-15 17:45:54 -04:00
Donald Zou
1a70acc6f2 Update documentation 2024-08-15 16:55:34 -04:00
Donald Zou
ee0a287112 Update README.md 2024-08-15 00:56:28 -04:00
Donald Zou
cf16a66c63 Update README.md 2024-08-15 00:53:38 -04:00
Donald Zou
4544b17c94 Update README.md 2024-08-15 00:53:23 -04:00
Donald Zou
8119c9da51 Update README.md 2024-08-15 00:49:21 -04:00
Donald Zou
baf4d75c01 Update documentations 2024-08-15 00:48:27 -04:00
Donald Zou
ac9eff0fcc Removed images 2024-08-14 23:59:50 -04:00
Donald Zou
dc57bd92ff Update README.md 2024-08-14 23:57:45 -04:00
Donald Zou
3c56a2c868 Update README.md 2024-08-14 23:55:52 -04:00
Donald Zou
2d2ab10a24 Update README.md 2024-08-14 23:54:54 -04:00
Donald Zou
ac906c9994 Update README.md 2024-08-14 23:53:28 -04:00
Donald Zou
480406d579 Update README.md 2024-08-14 23:52:14 -04:00
Donald Zou
47efb644b7 Working on some updates 2024-08-14 22:45:36 -04:00
Donald Zou
fd0e519e41 Fixed some API endpoint missed the prefix 2024-08-14 18:40:28 -04:00
Donald Zou
0c8bb990d0 Some UI adjustment 2024-08-14 14:35:49 -04:00
Donald Zou
bd7139827b Update 2024-08-14 12:31:37 -04:00
Donald Zou
4f648aff52 Updated UI for smaller screen 2024-08-14 12:07:53 -04:00
Donald Zou
3feb45dc01 Fixed for URL protocol 2024-08-14 10:59:01 -04:00
Donald Zou
0489dc7c33 Update fetch.js
Fixed for running over HTTPS
2024-08-14 10:57:41 -04:00
Donald Zou
a30843cff9 Added app prefix for URL 2024-08-14 01:17:47 -04:00
Donald Zou
3a34a0eb40 Adjusted some UI 2024-08-13 12:29:58 -04:00
Donald Zou
e3f82e136a Adjusted some UI 2024-08-12 18:04:41 -04:00
Donald Zou
8a7df4ba9f Update wgd.sh 2024-08-12 01:19:55 -04:00
Donald Zou
e86d1a4c7a Updated langugage 2024-08-12 00:34:54 -04:00
Donald Zou
5b9d0b60a1 Adjusted some UI 2024-08-11 19:20:52 -04:00
Donald Zou
7eff2f0c49 Fixed issue #250 2024-08-11 19:20:42 -04:00
Donald Zou
97236bb01d Fixed new configuration hang when error 2024-08-11 19:20:03 -04:00
Donald Zou
9c6aa12f48 Update README.md 2024-08-11 16:53:50 -04:00
Donald Zou
96ccb03eea Adjusted some code for electron version 2024-08-11 16:39:00 -04:00
Donald Zou
55f55820c5 Update wg-dashboard.service 2024-08-11 11:02:08 -04:00
Donald Zou
955839d513 I think cross server actually worked 2024-08-11 01:48:13 -04:00
Donald Zou
a650e628e5 CORS SUCCESS!!! 2024-08-10 19:23:50 -04:00
Donald Zou
54142b73fb Ohhhhh kay testing CORS :) 2024-08-10 19:03:21 -04:00
Donald Zou
55e0d2695d Update .gitignore 2024-08-10 12:58:41 -04:00
Donald Zou
2f90ab15dc Let's try ElectronJS 2024-08-10 12:58:14 -04:00
Donald Zou
fd3fc66bfc Thinking of adding Electron.js 2024-08-10 00:25:25 -04:00
Donald Zou
a352a94d8a Update .gitignore 2024-08-10 00:24:07 -04:00
Donald Zou
410b81f46f Some UI adjustment 2024-08-09 22:16:38 -04:00
Donald Zou
aa3711c5cc Adjusted search peer UI 2024-08-09 21:30:01 -04:00
Donald Zou
d6b1f97a04 Update 2024-08-09 20:46:11 -04:00
Donald Zou
b4e8e57a22 Updated UI for nav bar 2024-08-09 17:58:41 -04:00
Donald Zou
9644e6195c Finished adding data usage on configuration list 2024-08-09 17:30:44 -04:00
Donald Zou
764e0c7607 Replaced all cursor without recursive use 2024-08-09 17:29:57 -04:00
Donald Zou
97d640dd40 Update dashboard.py 2024-08-09 16:09:55 -04:00
Donald Zou
d2915b5b05 Update dashboard.py 2024-08-09 16:09:23 -04:00
Donald Zou
f274f6fd18 Fixed some bugs.. 2024-08-09 16:09:08 -04:00
Donald Zou
f507ac2569 Rewrote the add peer function 2024-08-08 23:27:13 -04:00
Donald Zou
208cbd6d89 Peer data reset function is don e 2024-08-08 20:56:14 -04:00
Donald Zou
cd2aa2902a Update README.md 2024-08-07 00:44:46 -04:00
Donald Zou
fa2d7fa3da Peer Sharing is done :) 2024-08-07 00:37:05 -04:00
Donald Zou
7463767781 Sharing peer is done, fixed #294
Fixed issue #294 with a Vue.js plugin instead of using the `datatime-local` input tag.

Still need to work on the end-user UI for sharing.
2024-08-06 19:15:00 -04:00
Donald Zou
958bc864c9 The sharing function is almost done 2024-08-06 10:17:14 -04:00
Donald Zou
4484668750 Backend for peer sharing is done 2024-08-05 19:57:17 -04:00
Donald Zou
d5dea4b87f Fixed the issue mentioned in #290 2024-08-05 17:55:58 -04:00
Donald Zou
0fdef6a0a2 fixing some Gunicorn bugs.. again.. 2024-08-05 15:39:11 -04:00
Donald Zou
bd71b6bad8 Update gunicorn.conf.py 2024-08-04 20:24:31 -04:00
Donald Zou
9b7887b279 Update gunicorn.conf.py 2024-08-04 20:23:41 -04:00
Donald Zou
3960e43872 Update gunicorn.conf.py 2024-08-04 20:22:41 -04:00
Donald Zou
201c8f9ec9 Update dashboard.py 2024-08-04 20:17:29 -04:00
Donald Zou
8c8374a08c Update wgd.sh 2024-08-04 20:16:26 -04:00
Donald Zou
467595afc9 Update wgd.sh 2024-08-04 20:15:46 -04:00
Donald Zou
acb54f098c Update gunicorn.conf.py 2024-08-04 20:13:17 -04:00
Donald Zou
5755d13460 Update gunicorn.conf.py 2024-08-04 19:45:28 -04:00
Donald Zou
2c3500315d Update gunicorn.conf.py 2024-08-04 19:36:49 -04:00
Donald Zou
47ea60c0cd Testing v4 2024-08-04 19:35:59 -04:00
Donald Zou
18b18c1396 Update wgd.sh 2024-08-04 19:33:05 -04:00
Donald Zou
ff227de5fa Update gunicorn.conf.py 2024-08-04 19:32:16 -04:00
Donald Zou
6799692811 Testing.. 2024-08-04 19:30:48 -04:00
Donald Zou
c6173f7f6f Update wgd.sh 2024-08-04 19:22:26 -04:00
Donald Zou
d0e4dabc44 Update wgd.sh 2024-08-04 19:22:06 -04:00
Donald Zou
f815dae300 Update wgd.sh 2024-08-04 19:21:38 -04:00
Donald Zou
b3bd6bb39e Update wgd.sh 2024-08-04 19:20:31 -04:00
Donald Zou
71df6409c2 Update gunicorn.conf.py 2024-08-04 19:17:41 -04:00
Donald Zou
e4f9a1e0cf Update gunicorn.conf.py 2024-08-04 19:03:30 -04:00
Donald Zou
ca6a05e393 Update gunicorn.conf.py 2024-08-04 18:59:45 -04:00
Donald Zou
c0d26164dc Update wgd.sh 2024-08-04 18:48:13 -04:00
Donald Zou
76fe2a1ba9 Update wgd.sh 2024-08-04 18:45:57 -04:00
Donald Zou
8cbdb54402 Still fixing Gunicron 2024-08-04 16:08:05 -04:00
Donald Zou
764ef80a62 Update wgd.sh 2024-08-04 15:44:55 -04:00
Donald Zou
0c37d93c01 Testing if this will fix bash exit before Gunicorn ran... 2024-08-04 15:29:07 -04:00
Donald Zou
c57a5128e5 Update wgd.sh 2024-08-04 03:20:44 -04:00
Donald Zou
6cf4eba20a Redirect when received a 401 error 2024-08-04 01:31:31 -04:00
Donald Zou
6825d728c2 Log function is completed 2024-08-03 17:03:39 -04:00
Donald Zou
6d3091b2a2 Push dockerfiles 2024-08-03 15:29:56 -04:00
Donald Zou
61473877a4 Update wgd.sh 2024-08-03 14:01:04 -04:00
Donald Zou
52989c8f5c Fixed clearInterval is not working within configuration 2024-08-03 13:26:16 -04:00
Donald Zou
b64ba2ef16 Fixed Gunicorn global variable issue 2024-08-03 13:25:57 -04:00
Donald Zou
461ae99dd8 Testing again... 2024-08-02 21:48:42 -04:00
Donald Zou
8681df6f02 Seems like Gunicorn need global 2024-08-02 21:45:55 -04:00
Donald Zou
ba081ee442 Update wgd.sh 2024-08-02 21:21:19 -04:00
Donald Zou
cf90d05115 Update wgd.sh 2024-08-02 18:30:16 -04:00
Donald Zou
658c6554af Update wgd.sh 2024-08-02 18:18:31 -04:00
Donald Zou
94d9d608f7 Update wgd.sh 2024-08-02 18:16:34 -04:00
Donald Zou
015b50be5f Doing some testing across distros 2024-08-02 17:27:28 -04:00
Donald Zou
85970f8c96 New build 2024-07-31 02:27:44 -04:00
Donald Zou
1740ab0bbe Update README.md 2024-07-31 02:23:20 -04:00
Donald Zou
9d2b5593a2 Add files via upload 2024-07-31 02:21:42 -04:00
Donald Zou
881d62d69d The UI and backend of API keys is done! 2024-07-30 18:45:05 -04:00
Donald Zou
935129f0a5 Finished the log system, now move on to something else... 2024-07-29 22:17:51 -04:00
Donald Zou
1a9bdc5e6d Merge pull request #288 from donaldzou/donaldzou-patch-3
Update README.md
2024-07-29 18:58:49 -04:00
Donald Zou
1f565bca10 Update README.md 2024-07-29 18:58:39 -04:00
Donald Zou
da089197a3 Changed the name 2024-07-29 18:56:56 -04:00
Donald Zou
2df8f41d6c Changed v4.0 preview GIF 2024-07-29 18:56:35 -04:00
Donald Zou
4e4f0d4c97 Create S1creen Recording 2024-07-29 at 5.59.53 PM.webm 2024-07-29 18:40:46 -04:00
Donald Zou
eaad22c0a1 Create proxy.js 2024-07-29 18:40:19 -04:00
Donald Zou
63e8553a09 Finished job logs :) 2024-07-29 18:40:07 -04:00
Donald Zou
b65828416f Job logs api are done, still need to build the UI to view logs 2024-07-27 18:51:43 -04:00
Donald Zou
48dc8033f5 Schedule system is finally running, still need to more testing :) 2024-07-01 00:58:02 +08:00
Donald Zou
2d838b69fd Peer schedule style is almost done
But I don't feel it quite right..
2024-06-25 23:02:13 +08:00
Donald Zou
6c529a6908 Fixed Gunicorn issue, continue on Peer Schedule Job 2024-06-19 17:09:58 +08:00
Donald Zou
9baefec541 Figuring out with Gunicorn... 2024-06-18 03:40:25 +08:00
Donald Zou
327d66bb80 Removed a tons of files :) 2024-06-18 03:16:42 +08:00
Donald Zou
760a4dfcb9 Still working on schedule.. 2024-06-16 23:32:32 +08:00
Donald Zou
ceb8cdd337 Merge pull request #281 from donaldzou/donaldzou-patch-3
Update README.md
2024-06-16 15:42:28 +08:00
Donald Zou
570c754eec Update README.md 2024-06-16 15:42:17 +08:00
Donald Zou
8ed75d1d21 Working on Job Scheduling 2024-06-16 15:40:10 +08:00
Donald Zou
54710b8221 Update .gitignore 2024-06-15 23:14:48 +08:00
Donald Zou
ff794033e1 Finished Traceroute 2024-06-13 16:56:06 +08:00
Donald Zou
f0f486da9e Adjusted some UI and finished the Ping function 2024-06-12 17:54:29 +08:00
Donald Zou
aedcfd1d24 Merge pull request #277 from DaanSelen/main
Added updated Docker functionality.
2024-06-11 22:40:38 +08:00
Daan
44e738acf5 Merge branch 'main' of https://github.com/DaanSelen/WGDashboard-dockerized 2024-06-10 20:36:13 +02:00
Daan
701b45c286 Improved (imo) the use of English. 2024-06-10 20:34:30 +02:00
dselen
72fe687d82 Update Docker-explain.md 2024-06-09 23:13:31 +02:00
dselen
632cd66b57 Update Docker-explain.md 2024-06-09 23:13:19 +02:00
Daan
20530c000e Added container documentation, and added a few new comments. 2024-06-09 23:11:22 +02:00
Daan
8824786fb4 For some reason, cloning the GitHub (which has been simplified) works from the Dockerfile directly.
But the wg0.conf file needs a re-copy on run-time. Working now.

Made code modifications and optimizations.

Please report bugs to me.
2024-06-08 13:30:24 +02:00
Daan
bdeb4a4efe Cleaned up lingering code line. 2024-06-07 14:55:16 +02:00
Daan
cf455fc19b Modified code so that the docker logging makes sense. Also fixed a bug which can happen with iptables (-s illegal option). Pushing to repo. 2024-06-07 13:25:46 +02:00
Daan
1bcddadb7a Features tested working, feeling good about a release. 2024-06-06 16:11:49 +02:00
Daan
6dc28f11e0 Working on isolated peers feature. 2024-06-06 00:12:11 +02:00
Daan
8ad601fcc0 Added features, plus explanations in about them in the compose.yaml file. Looking great! 2024-06-05 20:27:24 +02:00
Daan
f400844a3d Modified scripts 2024-06-05 09:16:29 +02:00
Daan
560096878f Persistency seems to work with this model, it has succeeded my tests. 2024-06-04 23:26:03 +02:00
Daan
0938f5ab71 Container deployed with working VPN server built-in. Missing is persistency among recreations, looking into that... 2024-06-04 22:49:17 +02:00
Daan
06193d27c0 Added dockerfile and entrypoint for basic compilation, moving to finalizing 2024-06-04 17:05:17 +02:00
Donald Zou
eb18857ecc Some UI adjustment 2024-06-03 02:16:09 +08:00
Donald Zou
9a280e99ad Finished restrict & allow access of peers 2024-06-02 23:22:43 +08:00
Donald Zou
c7ca20b45a Made some progress ;) 2024-05-20 22:28:52 +08:00
Donald Zou
60e64a3646 Merge pull request #269 from donaldzou/donaldzou-patch-2
Update README.md
2024-05-14 22:44:20 +08:00
Donald Zou
d60f89976e Update README.md 2024-05-14 22:44:09 +08:00
Donald Zou
41e05ddf9c Update dashboard_new.py
Yay adding peers in bulk is finally done ;)
2024-05-14 00:58:01 +08:00
Donald Zou
5a34f16dcf Update dashboard_new.py
Typo in `updatePeer`
2024-05-13 22:17:00 +08:00
Donald Zou
769ca4e26d Kind of finished revamping add peers
Still need to clean some of the codes but overall is good :)
2024-05-12 00:39:17 +08:00
Donald Zou
b6e62b08e4 Merge pull request #265 from donaldzou/v3.0.6.2
Update dashboard.py
2024-05-04 11:42:10 +08:00
Donald Zou
effe5b32fd Update dashboard.py 2024-05-04 11:40:32 +08:00
Donald Zou
9e38137e76 Removed DNS as a required field from peer settings 2024-05-03 10:15:00 +08:00
Donald Zou
57c2e89f00 Fixing some of the issue from users ;) 2024-04-26 00:03:42 +08:00
Donald Zou
914a0bf514 Minor updates... 2024-04-03 01:16:56 -04:00
Donald Zou
210f5eabc9 Merge pull request #261 from donaldzou/donaldzou-patch-1
Update FUNDING.yml
2024-03-31 11:20:04 -04:00
Donald Zou
5f5c91a8ff Update FUNDING.yml 2024-03-31 11:19:54 -04:00
Donald Zou
05c7121c8a Update README.md 2024-03-31 00:55:46 -04:00
Donald Zou
551a8e9588 Add files via upload 2024-03-31 00:53:00 -04:00
Donald Zou
75fbdb653a Update build 2024-03-24 22:55:19 -04:00
Donald Zou
bdfe75cff3 Finished download & QR Code 2024-03-24 22:46:32 -04:00
Donald Zou
bcd845fd59 Finished revamping peer edit 2024-03-24 18:24:01 -04:00
Donald Zou
f1e71ecb78 20240323 Commit
Finished implementing peer settings dropdown and planned how peer settings will be link
2024-03-21 02:03:08 -04:00
Donald Zou
0aa4c8af6f Continue to work on v4 ;0 2024-03-11 00:11:07 -04:00
Donald Zou
8fe5507ff8 Update README.md 2024-02-27 18:38:10 -05:00
Donald Zou
a950b80d5a v4 branch first commit! 2024-02-27 18:37:49 -05:00
Donald Zou
ed3bb6429b Finished some building blocks on the new version 2024-02-26 10:22:33 -05:00
Donald Zou
1e88491ca1 I am giving up on using ORM...
Lets go back to the good old sql query days ;)
2024-02-11 23:53:51 -05:00
Donald Zou
4aed647865 Merge pull request #254 from Upellift99/main
Enabling use of underscores in configuration name
2024-02-01 18:39:05 -05:00
John
74ab7aaa3d Enabling use of underscores in configuration name. 2024-02-01 09:53:04 +01:00
John
dcf2055851 Enabling use of underscores in configuration name. 2024-02-01 09:47:52 +01:00
Donald Zou
6b6ad05e3a The UI for New Configuration is done 2024-01-31 12:06:44 -05:00
Donald Zou
734a4b5e00 Merge pull request #252 from donaldzou/donaldzou-patch-1
Update README.md
2024-01-23 15:11:21 -05:00
Donald Zou
da70fac0b6 Update README.md 2024-01-23 15:11:02 -05:00
Donald Zou
5f4a364095 Huge update
A welcome session
Added Time based One-Time-Passcode (TOTP)
UI Update
2024-01-23 15:09:44 -05:00
Donald Zou
95a8867527 Update .gitignore 2024-01-11 13:47:35 -05:00
Donald Zou
7cb1301e80 Continue to refactor the UI and APIs :) 2024-01-11 13:46:08 -05:00
Donald Zou
e6e070d89e Continue to refactor the UI and APIs :) 2024-01-11 01:10:26 -05:00
Donald Zou
ba2bcaba07 Finally figured out SQLAlchemy and started to re-write some of the APIs. The UI will completely handle by JS with Vue. There will be no more templating from flask to minimize the resource usage ;) 2024-01-10 01:42:19 -05:00
Donald Zou
3d6ecba4f5 Update README.md 2024-01-09 14:10:08 -05:00
Donald Zou
864f82ba11 Started to refactor dashboard.py with dashboard_new.py and trying really hard to figure out sqlalchemy lol 2024-01-09 00:25:47 -05:00
Donald Zou
f671c992e1 testing something... 2024-01-08 12:23:57 -05:00
Donald Zou
86220694ce Update README.md
Added instruction to install `net-tools`. It is required for the Python package `ifcfg`. Seems like it is not pre-installed on a fresh debian based system.
2023-12-10 13:01:45 -05:00
Donald Zou
77f31177c8 Update .gitignore 2023-12-10 12:56:49 -05:00
Donald Zou
33ff91aea8 Update .gitignore 2023-12-10 12:56:02 -05: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
Donald Zou
0c0bce9755 Combining Vue.js!!! How exciting!
Adding Vue.js to handle frontend changes, leaving the server only need to response json data. Ditching flask template and hope it can reduce the memory and cpu usage :)
2023-11-30 09:42:02 -05:00
Donald Zou
f07508073f A new update to the dev branch 2023-11-28 16:37:16 -05:00
Donald Zou
2c9cade70a Create SECURITY.md 2023-03-23 13:07:36 -04:00
Donald Cheng Hong Zou
e06cc1bd2d Finally finished theme switching!!!! 2022-04-23 00:34:11 -04:00
Donald Cheng Hong Zou
36e33a4c10 Adjust the dark mode theme for PWA 2022-04-22 16:21:16 -04:00
Donald Cheng Hong Zou
7f668c1653 Some changes to dark mode css 2022-04-22 00:12:22 -04:00
Donald Zou
b464fa98df Merge pull request #173 from LeoEricson/v3.1-dev
Add dark theme
2022-04-21 22:33:40 -04:00
LeoEricson
23491f1e8c Add dark theme 2022-04-21 21:30:55 +00:00
Donald Cheng Hong Zou
2b90a2eed2 Update header.html 2022-04-21 15:11:35 -04:00
Donald Cheng Hong Zou
13b9d15d8f New darkmode theme 2022-04-21 15:11:01 -04:00
Donald Cheng Hong Zou
a053504bb8 Make the dashboard more mobile friendly 2022-04-11 20:01:29 -04:00
Donald Zou
d7d7a84bd5 Update README.md 2022-04-10 09:35:50 -04:00
Donald Zou
990463fbea Update README.md 2022-04-10 09:29:07 -04:00
Donald Cheng Hong Zou
dcdd4aec85 Update Peer Data Usage Graph 2022-04-10 09:26:54 -04:00
Donald Zou
0d9a8ba6f7 Update README.md 2022-04-06 21:05:24 -04:00
Donald Cheng Hong Zou
179da2ac05 Finished peer data usage chart 2022-04-06 20:59:23 -04:00
Donald Cheng Hong Zou
4848739b6e Added dockerfile! Thanks @ikidd! 2022-04-05 21:39:47 -04:00
Donald Cheng Hong Zou
46da285831 Adjusted js 2022-03-30 14:20:08 -04:00
Donald Cheng Hong Zou
71a6a36a54 Update dashboard.css 2022-03-30 14:06:05 -04:00
Donald Cheng Hong Zou
c8ca9ef7ab Minimized some js code 2022-03-30 00:54:11 -04:00
Donald Cheng Hong Zou
5af2fff9ca IPv6 configuration IP should be working now 2022-03-29 15:11:50 -04:00
Donald Zou
edbaf3ac82 Update README.md 2022-03-29 12:27:39 -04:00
Donald Cheng Hong Zou
337c9bc01e Update dashboard.py 2022-03-28 15:33:26 -04:00
Donald Zou
cd84674ae0 Update README.md 2022-03-28 08:23:12 -04:00
Donald Cheng Hong Zou
a196dce1fa Removed flask-socketio 2022-03-28 08:09:28 -04:00
Donald Zou
f60ea43f29 Create codeql-analysis.yml 2022-03-27 21:44:49 -04:00
Donald Zou
76f547a726 Update README.md 2022-03-26 11:14:27 -04:00
Donald Zou
dffe5e0819 Update README.md 2022-03-25 14:11:39 -04:00
Donald Cheng Hong Zou
b9633bbcd6 Finished implementing add/delete config 2022-03-24 20:43:56 -04:00
Donald Cheng Hong Zou
46efe2b8dd Finished developing add config 2022-03-24 02:10:52 -04:00
Donald Zou
fb9ef0c547 Update README.md 2022-03-22 17:34:34 -04:00
Donald Cheng Hong Zou
d52cd2b17c Doing a update 2022-03-22 17:28:19 -04:00
Donald Cheng Hong Zou
cefa80f317 Fixed redirect functionality 2022-03-22 16:17:12 -04:00
Donald Cheng Hong Zou
cc1dd682e8 Add configuration and adjusted redirect functionality 2022-03-22 16:13:57 -04:00
Donald Cheng Hong Zou
bdd984a887 Brand new switch button and toast UI 2022-03-21 22:33:19 -04:00
Donald Cheng Hong Zou
2d3dffe5fc Moved refresh interval and display mode to localStorage 2022-03-07 09:29:47 -05:00
Donald Cheng Hong Zou
65f31a0b38 Gave up using WebSocket
Flask-SocketIO does not fully compatible with Gunicorn, and it limited to 1 worker, which it will takes forever to finish loading the webpage. Switched back to  using ajax.
2022-03-04 22:09:01 -05:00
Donald Cheng Hong Zou
4a1a6c5933 Testing 2022-03-04 10:06:14 -05:00
Donald Cheng Hong Zou
7e1fd99c37 Fixed chart and updated requirement.txt 2022-03-04 08:28:54 -05:00
Donald Cheng Hong Zou
8fe8209580 Added line graph using chart.js & Improving websocket.
Added line graph to show total receive & total sent changes per refresh interval, using chart.js line chart to show the changes. Switching configuration don't need to refresh anymore, by using websocket.
2022-03-03 08:46:23 -05:00
Donald Cheng Hong Zou
264a050360 Temp fix 2022-02-28 13:34:46 -05:00
Donald Cheng Hong Zou
3623104e3b Merge branch 'main' into hmm-what-about-socket 2022-02-28 13:31:06 -05:00
Donald Cheng Hong Zou
191ff1abec Merge branch 'main' of https://github.com/donaldzou/WGDashboard 2022-02-28 13:29:25 -05:00
Donald Cheng Hong Zou
3bb86493cc Minor changes 2022-02-28 13:29:17 -05:00
Donald Zou
d1d3151e1e Update README.md 2022-02-27 15:50:55 -05:00
Donald Cheng Hong Zou
f9dc9ebdb3 Removed some unnecessary comments 2022-02-11 09:35:58 -05:00
Donald Cheng Hong Zou
3b478bcc2d OneDrive gone again.... 2022-02-04 21:36:46 -05:00
Donald Cheng Hong Zou
77bb78c381 OneDrive caused my whole project folder gone.... 2022-02-04 11:44:50 -05:00
Donald Cheng Hong Zou
cafa97f502 Changed version number 2022-01-31 16:09:50 -05:00
Donald Cheng Hong Zou
0d858493d5 Update README.md 2022-01-31 09:56:38 -05:00
Donald Cheng Hong Zou
99fb07c6b3 Fixed public key error with existed private key 2022-01-31 09:38:05 -05:00
Donald Zou
eaad971c0a Update README.md 2022-01-24 22:06:38 -05:00
Donald Cheng Hong Zou
377abd87fd Commit 2022-01-24 20:11:35 -05:00
Donald Zou
af71176296 Merge pull request #133 from donaldzou/v3.0.3-pr
v3.0.3
2022-01-23 19:34:06 -05:00
Donald Cheng Hong Zou
69737177ef Changed version number to v3.0.3 2022-01-23 19:30:43 -05:00
Donald Cheng Hong Zou
efae1222c1 Update dashboard.py 2022-01-23 19:25:12 -05:00
Donald Cheng Hong Zou
3caea1a903 Update README.md 2022-01-23 19:21:43 -05:00
Donald Cheng Hong Zou
a43478d627 Fixed no init_dashboard() for debug mode the first time 2022-01-23 19:19:38 -05:00
Donald Cheng Hong Zou
ed2c3f43c7 Added tracking loading time 2022-01-19 10:43:39 -05:00
Donald Cheng Hong Zou
84c9846f7b Hmmmm... What about SocketIO? 2022-01-19 10:27:17 -05:00
Donald Cheng Hong Zou
fcfd816cec Remove pylint 2022-01-19 09:25:27 -05:00
Donald Cheng Hong Zou
2e3977e59c commit 2022-01-19 09:18:03 -05:00
Donald Cheng Hong Zou
3c68430336 Update pylint.yml 2022-01-19 09:12:57 -05:00
Donald Cheng Hong Zou
43afb86fa8 Update pylint.yml 2022-01-19 09:10:59 -05:00
Donald Cheng Hong Zou
194ccbdbb4 Update pylint.yml 2022-01-19 09:10:07 -05:00
Donald Cheng Hong Zou
7139e230cf Commit 2022-01-19 09:08:27 -05:00
Donald Zou
120d3b9f54 Update pylint.yml 2022-01-19 09:05:39 -05:00
Donald Zou
712460a040 Update pylint.yml 2022-01-19 09:03:35 -05:00
Donald Zou
be5594f1c9 Create pylint.yml 2022-01-19 09:01:35 -05:00
Donald Cheng Hong Zou
0a885117db Update README.md 2022-01-18 13:04:00 -05:00
Donald Zou
5b73654544 Update README.md 2022-01-18 12:43:37 -05:00
Donald Cheng Hong Zou
1485b78b7b Merge branch 'main' of https://github.com/donaldzou/WGDashboard 2022-01-18 12:43:20 -05:00
Donald Cheng Hong Zou
bfec57172a Update README.md 2022-01-18 12:43:11 -05:00
Donald Zou
9f8559c12d Update README.md 2022-01-18 12:34:40 -05:00
Donald Zou
83dde12ca9 Merge pull request #126 from donaldzou/Migrate-to-SQLite
Production branch for v3.0
2022-01-18 12:33:34 -05:00
Donald Cheng Hong Zou
d17888db4e Update README.md 2022-01-18 12:33:21 -05:00
Donald Cheng Hong Zou
096529af96 Experimental Functions 2022-01-18 12:16:10 -05:00
Donald Cheng Hong Zou
8a56b22635 Added manifest.json 2022-01-18 11:02:19 -05:00
Donald Cheng Hong Zou
dcdd353981 Removed unnecessary files 2022-01-18 10:49:41 -05:00
Donald Cheng Hong Zou
258564208b Finalizing everything. 2022-01-18 10:42:23 -05:00
Donald Cheng Hong Zou
e2e8222005 Upload manifest.json 2022-01-16 20:35:24 -05:00
Donald Cheng Hong Zou
75f2826805 Merge branch 'pr/93' into Migrate-to-SQLite 2022-01-13 09:44:47 -05:00
Donald Cheng Hong Zou
730af4f2ee Finishing documentation 2022-01-13 09:37:23 -05:00
Donald Zou
e5737ebd9d Merge branch 'main' into Migrate-to-SQLite 2022-01-12 21:41:41 -05:00
Donald Cheng Hong Zou
7f24904164 Bugs 2022-01-12 20:28:12 -05:00
Donald Cheng Hong Zou
9ff7198602 Still finalizing everything 2022-01-12 19:53:36 -05:00
Donald Cheng Hong Zou
6d56967a0f Getting ready for v3.0 2022-01-08 15:26:17 -05:00
Donald Cheng Hong Zou
584118805a Feature Added
Moved key generating to front-end, by using wireguard.js from WireGuard's official repository. Added "Add Peers by bulk" feature.
2022-01-06 15:17:43 -05:00
Donald Zou
68b3f01082 Merge pull request #127 from pgalonza/refactoring
Fixed misprint
2022-01-04 16:41:26 -05:00
Donald Cheng Hong Zou
77a82cb84b Finished adding available IP and related UI adjustment 2022-01-04 16:32:23 -05:00
Galonza Peter
844b19495d fixed misprint 2022-01-03 00:51:22 +03:00
Donald Cheng Hong Zou
b8b721f2bd Delete wgdashboard.db 2022-01-02 14:45:24 -05:00
Donald Cheng Hong Zou
4f93d48ac6 Successfully migrate to SQlite 2022-01-02 14:44:27 -05:00
Donald Zou
02d1bf5a2c Merge pull request #125 from pgalonza/refactoring
Refactoring
2022-01-02 14:41:24 -05:00
Galonza Peter
fb3606c2b7 Refactored
Added docstrings like sphinx style, small refactoring and added TODO
2022-01-02 16:35:39 +03:00
Galonza Peter
a5bc2cd48f wg_conf_path is global variable 2022-01-01 03:20:30 +03:00
Galonza Peter
72151ac6a3 db path is global variable 2022-01-01 02:57:59 +03:00
Donald Cheng Hong Zou
e2c8b3bf02 Removed Semaphore for configuration file 2021-12-31 13:29:37 -05:00
Donald Cheng Hong Zou
06dd641274 Minified all CSS and JS script 2021-12-30 15:21:25 -05:00
Donald Cheng Hong Zou
7d9bf01d27 Cleaned configuration.js
Removed unused functions and duplicated jQuery selector
2021-12-29 23:26:15 -05:00
Donald Cheng Hong Zou
b9553e0a05 Update dashboard.py
Finished refactoring some variable name
2021-12-29 16:15:00 -05:00
Donald Cheng Hong Zou
993dcaf235 Merge branch 'main' of https://github.com/donaldzou/WGDashboard 2021-12-29 15:30:52 -05:00
Donald Cheng Hong Zou
8d7c39bac4 Update dashboard.py
Wrapped all reading and writing to dashboard configuration into functions
2021-12-29 15:29:29 -05:00
Donald Zou
80933ab571 Merge pull request #124 from pgalonza/fix-variables
Fixed variables name
2021-12-29 14:57:53 -05:00
Donald Cheng Hong Zou
b5b30c8119 Update dashboard.py 2021-12-29 14:57:44 -05:00
Galonza Peter
bca06129d9 fixed variables name 2021-12-29 22:56:19 +03:00
Donald Zou
a136b86d81 Merge pull request #122 from pgalonza/change-path
Fixed variable name
2021-12-29 14:20:52 -05:00
Donald Zou
8d555d8ae7 Merge pull request #121 from pgalonza/refactoring
Refactoring
2021-12-29 13:57:21 -05:00
Donald Zou
c22e61add2 Merge branch 'main' into refactoring 2021-12-29 13:57:11 -05:00
Donald Zou
2c83f1ce57 Merge pull request #120 from pgalonza/fix-download
Fixed download configuration file for peer
2021-12-29 12:21:29 -05:00
Donald Cheng Hong Zou
f2750de153 Feature adding and UI adjustment
Added Preshared-Key to all configuration
2021-12-29 12:17:44 -05:00
Galonza Peter
e0934f11d6 Merge branch 'main' into fix-download 2021-12-29 02:00:36 +03:00
Galonza Peter
422924026a fixed variable name 2021-12-29 02:00:18 +03:00
Galonza Peter
332e22e534 Merge branch 'main' into refactoring
# Conflicts:
#	src/dashboard.py
2021-12-29 01:51:41 +03:00
Galonza Peter
c7bf30e18e pylint warnings 2021-12-29 00:59:10 +03:00
Galonza Peter
5ade5176c0 fixed download configuration file for peer 2021-12-28 22:55:52 +03:00
Donald Zou
f9e4fafe9f Merge pull request #96 from pgalonza/change-path
Added the ability to change the path for files to be saved
2021-12-27 21:05:34 -05:00
Donald Zou
201f2ef1f0 Merge branch 'main' into change-path 2021-12-27 21:05:25 -05:00
Donald Zou
32af4fdcca Merge pull request #119 from donaldzou/v3.0-beta
Finishing v3.0
2021-12-27 21:01:24 -05:00
Donald Cheng Hong Zou
d254b4cc39 Finishing v3.0 2021-12-27 21:01:02 -05:00
Galonza Peter
d0ad4e05bf general refactoring 2021-12-28 00:29:26 +03:00
Galonza Peter
c75854cb7b fixed after merge 2021-12-27 00:35:10 +03:00
Galonza Peter
8cc71913b3 fixed read configuration after merge 2021-12-26 23:17:19 +03:00
Galonza Peter
99abe6bcd2 Merge branch 'main' into change-path 2021-12-26 12:58:44 +03:00
Galonza Peter
f5ad7a49ce general refactoring 2021-12-26 12:57:57 +03:00
Donald Cheng Hong Zou
e42ecf3c99 Update wgd.sh 2021-12-25 17:45:47 -05:00
Donald Cheng Hong Zou
b58bf9dc17 Update wgd.sh 2021-12-25 17:40:53 -05:00
Donald Cheng Hong Zou
745c8aaf3e Update wgd.sh 2021-12-25 17:40:08 -05:00
Donald Cheng Hong Zou
0ca73bf80b Update wgd.sh 2021-12-25 17:38:13 -05:00
Donald Cheng Hong Zou
fd76cabd01 Update wgd.sh 2021-12-25 17:37:43 -05:00
Donald Cheng Hong Zou
fcf9300a49 Update wgd.sh 2021-12-25 17:35:36 -05:00
Donald Cheng Hong Zou
649b66ba1a Update wgd.sh 2021-12-25 17:20:52 -05:00
Donald Cheng Hong Zou
b5d73a0861 Delete hi.txt 2021-12-25 17:20:31 -05:00
Donald Cheng Hong Zou
1453996ccb Update wgd.sh 2021-12-25 17:16:50 -05:00
Donald Cheng Hong Zou
5bf9b97d70 Update wgd.sh 2021-12-25 17:15:33 -05:00
Donald Cheng Hong Zou
5c1e46f5a6 Update wgd.sh 2021-12-25 17:13:21 -05:00
Donald Cheng Hong Zou
c2665cbae8 Update wgd.sh 2021-12-25 17:13:08 -05:00
Donald Cheng Hong Zou
3934248a6b Update wgd.sh 2021-12-25 17:08:59 -05:00
Donald Cheng Hong Zou
7ae3bb5601 Testing update function 2021-12-25 17:07:33 -05:00
Donald Cheng Hong Zou
9890d4f3bc Removed using venv for now 2021-12-25 16:29:33 -05:00
Donald Cheng Hong Zou
5b47fc16d9 Update wgd.sh 2021-12-25 16:08:30 -05:00
Galonza Peter
344770a0a4 Merge branch 'main' into change-path
# Conflicts:
#	src/dashboard.py
2021-12-25 23:42:10 +03:00
Donald Cheng Hong Zou
fc11cacd24 Update wgd.sh 2021-12-25 15:20:56 -05:00
Donald Cheng Hong Zou
737ebdd0c6 Update wgd.sh 2021-12-25 15:20:04 -05:00
Galonza Peter
d06dddfa24 Merge branch 'main' into change-path 2021-12-25 23:17:40 +03:00
Donald Cheng Hong Zou
710d631ffe Update wgd.sh 2021-12-25 15:17:07 -05:00
Donald Cheng Hong Zou
7d9a845ca0 Update wgd.sh 2021-12-25 14:48:38 -05:00
Donald Cheng Hong Zou
cc6e5ffb97 Update wgd.sh 2021-12-25 14:46:47 -05:00
Donald Cheng Hong Zou
8d29cb7488 Testing Python venv 2021-12-25 14:44:14 -05:00
Donald Cheng Hong Zou
5c588ea01a Style adjustment
Adjusted style and the status light: Added box-shadow and border for connected peer
2021-12-24 00:20:44 -05:00
Donald Zou
dade9935da Merge pull request #117 from donaldzou/v3.0-Slow-Load-Fix
Slow Performance Fixed
2021-12-24 00:19:26 -05:00
Donald Cheng Hong Zou
34cd3aaaca Slow Performance Fixed 2021-12-23 21:26:24 -05:00
Donald Cheng Hong Zou
78d3f338fb Update dashboard.py 2021-12-22 11:36:29 -05:00
Donald Zou
d801c5911e Merge pull request #91 from pgalonza/todo-ipv6
Add IPv6
2021-12-22 11:35:17 -05:00
Galonza Peter
003743c074 Merge branch 'main' into gunicorn 2021-12-21 22:41:03 +03:00
Galonza Peter
4cef39d3b1 Merge branch 'main' into gunicorn-tmp
# Conflicts:
#	src/wgd.sh
2021-12-21 22:38:31 +03:00
Galonza Peter
83f0df32d7 added thread 2021-12-21 22:23:26 +03:00
Galonza Peter
24f269191c added Let’s Encrypt via certbot 2021-12-21 22:23:13 +03:00
Galonza Peter
94a0d5a0a4 refactored 2021-12-21 22:23:05 +03:00
Galonza Peter
c0fbf4dd0c optimized performance
# Conflicts:
#	src/gunicorn.conf.py
2021-12-21 22:22:37 +03:00
Galonza Peter
37b6161075 fixed check gunicorn process 2021-12-21 22:20:43 +03:00
Galonza Peter
c9b792c370 fixed update variable 2021-12-21 22:20:31 +03:00
Galonza Peter
2103d547a1 fixed initiation for gunicorn 2021-12-21 22:18:36 +03:00
Galonza Peter
3918e220d2 style 2021-12-21 22:17:32 +03:00
Galonza Peter
c3eaaed43b fixed stop 2021-12-21 22:17:07 +03:00
Galonza Peter
e0bf648076 added paths when command as root 2021-12-21 22:16:40 +03:00
Galonza Peter
bb298164e4 added gunicorn in requirements 2021-12-21 22:15:55 +03:00
Galonza Peter
898694b9be fixed gunicorn start and added stop 2021-12-21 22:15:41 +03:00
Galonza Peter
f0f9ac92e6 added gunicorn start 2021-12-21 22:15:23 +03:00
Galonza Peter
ea0229a8ab added wsgi for gunicorn 2021-12-21 22:15:08 +03:00
Donald Zou
66fc7cd91d Merge pull request #112 from reafian/main
Added MTU and PersistentKeepalive to QR code and download files
2021-12-21 14:12:42 -05:00
Donald Zou
c11d4f01b1 Merge pull request #114 from donaldzou/all-contributors/add-marneu
docs: add marneu as a contributor for code
2021-12-21 13:54:03 -05:00
Donald Zou
a8f498f292 Merge pull request #113 from donaldzou/marneu-main
Marneu main
2021-12-21 13:53:51 -05:00
allcontributors[bot]
6854e2275f docs: update .all-contributorsrc [skip ci] 2021-12-21 18:53:40 +00:00
allcontributors[bot]
6662e79ac5 docs: update README.md [skip ci] 2021-12-21 18:53:39 +00:00
Donald Cheng Hong Zou
9e77430634 Added Slash 2021-12-21 13:52:08 -05:00
Richard Newton
b8e070824a Update get_conf.html
Added MTU and PersistentKeepalive back into QR Code.

Update dashboard.py

Added MTU and PersistentKeepalive to downloaded file.
2021-12-21 16:48:11 +00:00
Donald Cheng Hong Zou
304d4293f3 Update wgd.sh 2021-12-20 19:54:53 -05:00
Donald Zou
a03e7f5b53 Merge pull request #111 from donaldzou/marneu-main
Marneu main
2021-12-19 18:18:50 -05:00
Donald Zou
ea57c8cad4 Merge branch 'main' into marneu-main 2021-12-19 18:18:45 -05:00
Donald Cheng Hong Zou
280802cf74 Update .gitignore 2021-12-19 18:16:45 -05:00
Donald Cheng Hong Zou
fbce01d846 Update .gitignore 2021-12-19 18:15:51 -05:00
Markus Neubauer
591b60c336 Update wgd.sh
a more automated approach to systemctl
2021-12-19 18:13:15 -05:00
Markus Neubauer
78ef870d91 Update wg-dashboard.service
A more automated approach
2021-12-19 18:13:15 -05:00
Donald Zou
bc19d82aed Merge pull request #105 from marneu/patch-1
An automated way to a systemctl unit
2021-12-19 18:07:35 -05:00
Donald Zou
79b41e1056 Update README.md 2021-12-06 14:15:22 -05:00
Markus Neubauer
805f03d231 Update .gitignore 2021-11-25 16:03:09 +01:00
Donald Zou
62acc1081f Update README.md 2021-11-19 14:48:46 -05:00
Donald Zou
257a2c2d9f Update README.md 2021-11-19 14:45:37 -05:00
Donald Zou
ed9b05cdf9 Update README.md 2021-11-19 14:44:40 -05:00
Donald Zou
2cc6fbd643 Update README.md 2021-11-10 11:40:46 -05:00
Galonza Peter
bdfc260dd2 fixed 2021-10-24 13:32:21 +03:00
Galonza Peter
7ad65956c9 fixed, if dir is exists 2021-10-24 12:45:08 +03:00
Galonza Peter
b3dc60b7ca fixed check ipv6 dns 2021-10-24 12:05:14 +03:00
Galonza Peter
bed08109fa not needed 2021-10-24 02:10:20 +03:00
Galonza Peter
06cedec090 mistake 2021-10-24 01:58:59 +03:00
Galonza Peter
1d2c468d03 fixed, when database does not create, if no directory 2021-10-24 01:51:04 +03:00
Galonza Peter
e84616f5f5 added the ability to change the path for files to be saved 2021-10-24 00:56:34 +03:00
Galonza Peter
caa9a90476 added dual-stack 2021-10-17 22:55:08 +03:00
Galonza Peter
62be683dd8 fixed search ipv6 in the string with comma 2021-10-17 21:13:18 +03:00
Galonza Peter
ffa63b3f82 completed todo. Added IPv6 2021-10-17 20:22:27 +03:00
Galonza Peter
c5a7788394 optimized pattern 2021-10-17 20:01:16 +03:00
Donald Zou
99c42ff2c1 Merge pull request #90 from pike00/pike00-update-readme
Updated README to include username/password
2021-10-14 10:14:38 -04:00
Will Pike
fd2b7bb917 Updated README to include username/password
Updated Installation instructions to include default username/password in step 6 on login. It is located in the 'Dashboard Configuration' section, however for readability/ease of setting up the server, I added the default login to the install instructions.
2021-10-14 10:10:17 -04:00
Donald Cheng Hong Zou
922a494df0 Finished 2021-09-09 12:50:38 -04:00
Donald Cheng Hong Zou
c478ec0095 Update README.md 2021-09-09 12:45:09 -04:00
Donald Cheng Hong Zou
3abbe17910 Naming changed 2021-09-09 12:43:49 -04:00
Donald Zou
349076599f Merge pull request #80 from ikidd/patch-1
Fixed service unit file spelling error
2021-09-09 12:35:44 -04:00
ikidd
92d299507f Fixed service unit file spelling error 2021-09-08 20:36:53 -06:00
Donald Cheng Hong Zou
6f2d61e918 Update README.md 2021-09-08 22:02:33 -04:00
Donald Cheng Hong Zou
ed13babf6a Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2021-09-08 22:01:01 -04:00
Donald Cheng Hong Zou
80db82eb93 Update dashboard.py 2021-09-08 22:00:53 -04:00
Donald Zou
8d20334d6e Update README.md 2021-09-08 21:59:15 -04:00
Donald Zou
03a95ad018 Merge pull request #79 from donaldzou/v2.3.1
Update README.md
2021-09-08 21:58:02 -04:00
Donald Cheng Hong Zou
aa59cc8af0 Update README.md 2021-09-08 21:57:50 -04:00
Donald Zou
80d63c2c89 Merge pull request #78 from donaldzou/v2.3.1
Changed dashboard naming to WGDashboard
2021-09-08 21:56:48 -04:00
Donald Cheng Hong Zou
03a4acd72b Changed dashboard naming to WGDashboard 2021-09-08 21:56:31 -04:00
Donald Cheng Hong Zou
53118cf660 Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2021-09-08 16:58:40 -04:00
Donald Cheng Hong Zou
16b1baf81e Update README.md 2021-09-08 16:58:36 -04:00
Donald Zou
960e0d0965 Update README.md 2021-09-08 14:50:26 -04:00
Donald Zou
8f41222e43 Merge pull request #77 from donaldzou/v2.3
Update README.md
2021-09-08 12:43:09 -04:00
Donald Cheng Hong Zou
e7bc447166 Update README.md 2021-09-08 12:42:50 -04:00
Donald Zou
1736359615 Merge pull request #76 from donaldzou/v2.3
V2.3
2021-09-08 12:42:11 -04:00
Donald Cheng Hong Zou
58784b1e22 v2.3 Final Commit 2021-09-08 12:39:25 -04:00
Donald Zou
07f2bfd279 Merge pull request #75 from donaldzou/all-contributors/add-davejlong
docs: add davejlong as a contributor for code
2021-09-08 11:24:04 -04:00
allcontributors[bot]
6d62db2890 docs: update .all-contributorsrc [skip ci] 2021-09-08 15:23:38 +00:00
allcontributors[bot]
a1b23c4aab docs: update README.md [skip ci] 2021-09-08 15:23:37 +00:00
Donald Zou
e272a096da Merge pull request #74 from donaldzou/all-contributors/add-reafian
docs: add reafian as a contributor for code
2021-09-05 16:38:57 -04:00
allcontributors[bot]
b8ca24c5bf docs: update .all-contributorsrc [skip ci] 2021-09-05 20:38:34 +00:00
allcontributors[bot]
a05d09f0de docs: update README.md [skip ci] 2021-09-05 20:38:33 +00:00
Donald Zou
1807a79c4f Create FUNDING.yml 2021-09-04 17:16:31 -04:00
Donald Cheng Hong Zou
77f6826af9 Adjusted UI and edit readme 2021-09-03 17:32:51 -04:00
Donald Cheng Hong Zou
7e39eedf35 Update wgd.sh 2021-09-03 14:40:57 -04:00
Donald Cheng Hong Zou
0b4b48ceae Update wgd.sh 2021-09-03 14:30:13 -04:00
Donald Cheng Hong Zou
dad931e898 Update wgd.sh 2021-09-03 14:26:27 -04:00
Donald Cheng Hong Zou
2e387730cb Update wgd.sh 2021-09-03 14:26:05 -04:00
Donald Cheng Hong Zou
c9b6775793 Update wgd.sh 2021-09-03 14:24:38 -04:00
Donald Cheng Hong Zou
a6eaf30c2b Update wgd.sh 2021-09-03 14:24:14 -04:00
Donald Cheng Hong Zou
6f91d199d8 Update wgd.sh 2021-09-03 14:23:08 -04:00
Donald Cheng Hong Zou
7ea486307a Update wgd.sh 2021-09-03 14:22:14 -04:00
Donald Cheng Hong Zou
37d254c827 Update wgd.sh 2021-09-03 13:55:41 -04:00
Donald Cheng Hong Zou
b4014eb317 Update wgd.sh 2021-09-03 13:55:03 -04:00
Donald Cheng Hong Zou
c7824fa184 Update wgd.sh 2021-09-03 13:52:23 -04:00
Donald Cheng Hong Zou
13c5bd45f9 Update wgd.sh 2021-09-02 22:52:22 -04:00
Donald Cheng Hong Zou
465d102bd0 Update wgd.sh 2021-09-02 22:52:01 -04:00
Donald Cheng Hong Zou
93007cd56c Update wgd.sh 2021-09-02 22:27:05 -04:00
Donald Cheng Hong Zou
8367576dc8 Update wgd.sh 2021-09-02 22:24:40 -04:00
Donald Cheng Hong Zou
dbc06145b3 Update wgd.sh 2021-09-02 22:24:07 -04:00
Donald Cheng Hong Zou
4efcf0e7c8 Update wgd.sh 2021-09-02 22:13:00 -04:00
Donald Cheng Hong Zou
0a4bbc1106 Update wgd.sh 2021-09-02 22:09:06 -04:00
Donald Cheng Hong Zou
b08c1f6699 Updated requirement.txt and wgd.sh 2021-09-02 21:56:50 -04:00
Donald Cheng Hong Zou
6828b905ef Added checking if peers removed directly from config file by not from dashboard, and fixed DNS did not get check from editing peers 2021-08-24 21:04:01 -04:00
Donald Zou
f72b405f6d Merge pull request #64 from Kofl/patch-1
Update typo3 fix
2021-08-24 20:19:22 -04:00
Donald Zou
27ae99864c Merge pull request #71 from donaldzou/v2.2.2
v2.2.2 Merge
2021-08-24 20:16:21 -04:00
Donald Cheng Hong Zou
82ba15ba5a Small fix on HTML files 2021-08-24 20:15:28 -04:00
Kofl
af01db7bc9 Update typo3 fix
TOTAL RECIEVED replaced with TOTAL RECEIVED
2021-08-18 21:46:06 +02:00
Donald Cheng Hong Zou
6ec0a65d0c Update README.md 2021-08-16 15:32:14 -04:00
Donald Zou
c32ccfc9ed Merge pull request #51 from donaldzou/v2.2.1
V2.2.1
2021-08-16 15:29:49 -04:00
Donald Cheng Hong Zou
ef818388c8 Update dashboard.py 2021-08-16 15:28:43 -04:00
Donald Cheng Hong Zou
f5aa45a3e1 Update README.md 2021-08-16 15:27:10 -04:00
Donald Cheng Hong Zou
b7a047a743 Bug fixed 2021-08-16 15:26:15 -04:00
Donald Zou
05ec985453 Update README.md 2021-08-16 12:23:56 -04:00
Donald Cheng Hong Zou
88ed9c3e79 Update README.md 2021-08-15 02:23:35 -04:00
Donald Zou
3fb03c8dfe Update README.md 2021-08-15 00:16:36 -04:00
Donald Zou
ff304027cd Merge pull request #46 from donaldzou/v2.2-beta
V2.2 pull request
2021-08-14 23:57:07 -04:00
Donald Cheng Hong Zou
3140d46c94 Finalizing the documentation 2021-08-14 23:43:30 -04:00
Donald Cheng Hong Zou
9d476af384 v2.2beta almost done!!!!! 2021-08-14 23:30:05 -04:00
Donald Cheng Hong Zou
0d380672f3 v2.2 beta 4 2021-08-14 17:13:16 -04:00
Donald Cheng Hong Zou
e2fb8dca5b Update README.md 2021-08-06 00:08:30 -04:00
Donald Cheng Hong Zou
eaba4c90c5 Edit Readme 2021-08-06 00:00:32 -04:00
Donald Cheng Hong Zou
b4ca7373f3 Update Readme 2021-08-05 23:47:18 -04:00
Donald Cheng Hong Zou
905c068ed5 v2.2-beta4
Finished testing the QR code function and finish debugging the peer file download function.
2021-08-05 23:15:50 -04:00
Donald Cheng Hong Zou
02886dd420 Remove public and private key txt 2021-08-05 01:39:32 -04:00
Donald Cheng Hong Zou
5bf7754d6c Update README.md 2021-08-05 01:21:44 -04:00
Donald Cheng Hong Zou
7a0a3055c4 Update README.md 2021-08-05 01:16:22 -04:00
Donald Cheng Hong Zou
bf73efb3b4 Update README.md 2021-08-05 01:15:17 -04:00
Donald Cheng Hong Zou
b4da212158 Update README.md 2021-08-05 01:07:10 -04:00
Donald Cheng Hong Zou
4a04d42600 v2.2-beta3
Finished QR code function and starting to test.
2021-08-05 00:45:15 -04:00
Donald Cheng Hong Zou
42bbfbe538 v2.2-beta update 2021-08-03 18:45:40 -04:00
Donald Cheng Hong Zou
31ec2430c4 Update README.md 2021-08-03 18:06:43 -04:00
Donald Cheng Hong Zou
f0b56c9f1d Update README.md 2021-08-03 18:06:22 -04:00
Donald Zou
6f9f01c991 Update README.md 2021-07-12 16:35:07 -04:00
Donald Cheng Hong Zou
b822ccb19d Update README.md 2021-07-11 13:07:27 -04:00
Donald Zou
dcb11671f3 Update README.md 2021-07-11 12:49:58 -04:00
Donald Zou
282599d36f Update README.md 2021-07-05 16:09:02 -04:00
Donald Zou
f11acdc68f Update README.md 2021-07-05 15:35:23 -04:00
Donald Zou
971e127cfd Update README.md 2021-07-03 10:30:54 -04:00
Donald Zou
9b2c59d529 Delete wg0.conf 2021-07-02 14:35:34 -04:00
Donald Zou
14dc049a66 Update README.md 2021-07-02 14:31:33 -04:00
Donald Zou
d3d0e7c615 Merge pull request #24 from donaldzou/v2.1-beta
v2.1 Merge
2021-07-02 13:28:36 -04:00
Donald Zou
89423ccb8b Merge branch 'main' into v2.1-beta 2021-07-02 13:28:29 -04:00
Donald Cheng Hong Zou
746f31f70c Update README.md 2021-07-02 13:25:35 -04:00
Donald Cheng Hong Zou
3aa9eab2bd Commit June 2 2021-07-02 13:23:04 -04:00
Donald Zou
1bcc8fed5e Update README.md 2021-07-02 12:00:12 -04:00
Donald Zou
1213a030e3 Update README.md 2021-05-13 19:54:37 -04:00
Donald Zou
3b2344b477 Delete .github/workflows directory 2021-05-13 19:51:54 -04:00
Donald Zou
c53b0fe2d4 Create main.yml 2021-05-13 19:38:22 -04:00
Donald Cheng Hong Zou
0a92269456 v2.0-beta-6 Commit 2021-05-13 18:47:59 -04:00
Donald Cheng Hong Zou
1cb8906893 v2.0-beta-6 Commit 2021-05-13 18:21:10 -04:00
Donald Cheng Hong Zou
f76579fe5e v2.0-beta-6 Commit 2021-05-13 18:00:40 -04:00
Donald Zou
db05ff5e98 Update README.md 2021-05-08 12:35:22 -04:00
Donald Zou
a05d6979ec Update README.md 2021-05-07 15:27:37 -04:00
Donald Zou
be40d17172 Update README.md 2021-05-07 15:25:23 -04:00
Donald Zou
9e11339979 Update README.md 2021-05-06 11:20:28 -04:00
Donald Zou
a56129afe0 Update README.md 2021-05-05 17:25:23 -04:00
Donald Zou
8933ea999c Update README.md 2021-05-05 15:52:13 -04:00
Donald Zou
ae3b8f6494 Merge pull request #19 from donaldzou/v2.0-beta-6
v2.0 merge to main
2021-05-05 15:29:41 -04:00
Donald Cheng Hong Zou
33487ee03f v2.0-beta-6 Commit 2021-05-05 15:16:32 -04:00
Donald Cheng Hong Zou
58ecae1162 v2.0-beta-6 Commit 2021-05-05 15:09:34 -04:00
Donald Cheng Hong Zou
8777da10e4 v2.0-beta-1 Commit 2021-05-05 14:59:00 -04:00
Donald Zou
4ea971c1b9 v2.0-beta-1 Commit 2021-05-05 14:52:28 -04:00
Donald Cheng Hong Zou
0fa4759c3a Update 2021-05-05 14:38:10 -04:00
Donald Cheng Hong Zou
fe7b9730d3 Update README.md 2021-05-04 21:41:13 -04:00
Donald Cheng Hong Zou
040f6a8393 Commit 2021-05-04 21:26:40 -04:00
Donald Cheng Hong Zou
b17575b197 Update requirements.txt 2021-05-04 15:55:52 -04:00
Donald Cheng Hong Zou
dee4f757e7 Upload README pictures 2021-05-04 15:42:04 -04:00
Donald Cheng Hong Zou
5ae08d02ab Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2021-05-04 02:10:32 -04:00
Donald Cheng Hong Zou
b83aec4965 v2.0-beta-2 Commit 2021-05-04 02:10:06 -04:00
Donald Zou
68c4837780 Merge pull request #18 from donaldzou/testing
Update .gitignore
2021-05-04 01:39:51 -04:00
Donald Cheng Hong Zou
46fe5e99fe Update .gitignore 2021-05-04 01:39:32 -04:00
Donald Zou
9ffb537cfb Delete wg-dashboard.ini 2021-05-04 01:39:14 -04:00
Donald Zou
cc7e6f852d Merge pull request #17 from donaldzou/testing
v2.0-beta1 Merge
2021-05-04 01:37:27 -04:00
Donald Cheng Hong Zou
f83e99c62c Merge branch 'testing' of https://github.com/donaldzou/Wireguard-Dashboard into testing 2021-05-04 01:36:32 -04:00
Donald Zou
bc8b8e7982 v2.0-beta-1 Commit 2021-05-04 01:32:34 -04:00
Donald Zou
3b761ca2a8 Update README.md 2021-05-04 01:20:20 -04:00
Donald Cheng Hong Zou
edc21f9830 Update README.md 2021-04-12 23:18:57 -04:00
Donald Cheng Hong Zou
d025ec4dff Push Logo 2021-04-12 23:16:12 -04:00
Donald Cheng Hong Zou
ab334a393f Logo 2021-04-12 23:15:53 -04:00
Donald Cheng Hong Zou
2a9044086f Push Logo 2021-04-12 23:14:53 -04:00
Donald Zou
c3038ec60c Merge pull request #14 from donaldzou/all-contributors/add-tonjo
docs: add tonjo as a contributor
2021-04-12 22:30:23 -04:00
allcontributors[bot]
0fa827ba00 docs: update .all-contributorsrc [skip ci] 2021-04-13 02:29:48 +00:00
allcontributors[bot]
26002fc372 docs: update README.md [skip ci] 2021-04-13 02:29:47 +00:00
Donald Zou
59acba8c08 Update README.md 2021-04-12 22:29:00 -04:00
Donald Zou
4c5ee45fdb Update README.md 2021-04-12 22:28:30 -04:00
Donald Zou
6062120d0d Merge pull request #13 from donaldzou/all-contributors/add-antonioag95
docs: add antonioag95 as a contributor
2021-04-12 22:27:54 -04:00
allcontributors[bot]
cd3ffb2126 docs: create .all-contributorsrc [skip ci] 2021-04-13 02:26:56 +00:00
allcontributors[bot]
a8c11cc445 docs: update README.md [skip ci] 2021-04-13 02:26:55 +00:00
Donald Zou
fcb89aac6b Update README.md 2021-04-12 11:05:19 -04:00
Donald Zou
3704d8f6e7 Merge pull request #11 from tonjo/patch-1
Avoid error when `AllowedIPs` is not defined for a peer.
2021-04-12 11:01:38 -04:00
Donald Zou
8c75d172c5 Update issue templates 2021-04-10 12:49:34 -04:00
Donald Zou
5687302081 Update issue templates 2021-04-10 12:47:18 -04:00
Donald Zou
9020b16a9a Update README.md 2021-04-10 00:49:32 -04:00
tonjo
d5462df4da Update dashboard.py 2021-04-09 11:15:25 +02:00
tonjo
9021ca31af Avoid error when AllowedIps is not defined 2021-04-09 11:08:41 +02:00
Donald Zou
83cb8b7f03 Update README.md 2021-04-09 00:42:42 -04:00
Donald Cheng Hong Zou
baf2658eb8 v20210409 2021-04-09 00:07:37 -04:00
Donald Zou
3f19b05d28 Update README.md 2021-04-07 17:25:21 -04:00
Donald Zou
5e6be1f830 Update README.md 2021-04-07 15:58:00 -04:00
Donald Zou
14d1a3038f Update README.md 2021-04-07 12:19:23 -04:00
Donald Zou
6a15e2e587 Update README.md 2021-04-07 12:15:45 -04:00
Donald Zou
0679805728 Update README.md 2021-04-07 12:01:35 -04:00
Donald Zou
62a03419a5 Update README.md 2021-04-07 11:52:24 -04:00
Donald Zou
81f6b0767e Update README.md 2021-04-07 11:52:03 -04:00
Donald Zou
c7ca5c749b Update README.md 2021-04-06 19:52:33 -04:00
Donald Zou
1daac09b7c Update README.md 2021-04-06 19:51:50 -04:00
Donald Zou
4d3dd68ac0 Merge pull request #5 from antonioag95/main
- Fixed a bug where a peer without last handshake date was showing wrong date in UI
- Hidden and temp `.conf` files will be excluded from interfaces list
2021-04-06 19:43:52 -04:00
Donald Zou
de9e4a6270 Update README.md 2021-04-06 16:51:37 -04:00
Donald Zou
bf8f347245 Update README.md 2021-04-06 15:03:49 -04:00
Donald Zou
a70d09ac7a Update README.md 2021-04-06 10:14:11 -04:00
Donald Zou
919b344da4 Update README.md 2021-04-05 22:41:55 -04:00
antonioag95
44f013a678 Update dashboard.py
- Fixed a bug where a peer without last handshake date was showing wrong date in webui
- Hidden and temp conf files will be excluded from interfaces list
2021-04-03 23:48:07 +02:00
Donald Zou
742191111b Merge pull request #4 from donaldzou/v20210403-alpha
v20210403 Bug Fixed
2021-04-03 14:31:11 -04:00
Donald Cheng Hong Zou
8dc0031475 Update dashboard.py 2021-04-03 14:30:02 -04:00
Donald Cheng Hong Zou
19fb90a300 Update dashboard.py 2021-04-03 14:10:07 -04:00
Donald Cheng Hong Zou
0c3960d8d9 Commit 2021-04-03 14:06:21 -04:00
Donald Cheng Hong Zou
7e770b9b5d v20210402.2 2021-04-02 22:22:41 -04:00
Donald Zou
b95f391f02 Update README.md 2021-04-02 21:15:50 -04:00
Donald Zou
4ed861ffce Update README.md 2021-04-02 21:15:28 -04:00
Donald Zou
2c0b3be900 Update README.md 2021-04-02 21:14:51 -04:00
Donald Cheng Hong Zou
145e163670 Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2021-04-02 21:14:06 -04:00
Donald Cheng Hong Zou
d5497843eb v20210403.2 2021-04-02 21:12:46 -04:00
Donald Zou
3799f6fe49 Update README.md 2021-04-02 20:49:41 -04:00
Donald Cheng Hong Zou
07678300da v20210402
Added more features
2021-04-02 20:48:00 -04:00
Donald Zou
881c8f30ef Update README.md 2021-03-24 02:07:01 -04:00
Donald Zou
5d02fbbdea Update README.md 2021-01-12 13:36:13 -05:00
donaldzou
4c969f5524 Update 2020-12-27 00:02:36 -05:00
donaldzou
f361e178f1 Added the function to remove peers 2020-12-26 23:42:41 -05:00
donaldzou
edc77be8ef Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2020-12-26 00:17:43 -05:00
donaldzou
55ba5801af Added connected peers 2020-12-26 00:17:42 -05:00
Donald Zou
6d9c35f7cc Update README.md 2020-10-28 10:12:39 -04:00
Donald Zou
320b82dc18 Update README.md 2020-10-23 17:51:11 -04:00
donaldzou
d22d695312 Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2020-10-23 01:51:03 -04:00
donaldzou
ba5eeeba07 Commit 2020-10-23 01:50:52 -04:00
Donald Zou
0b5b2b6243 Delete sftp.json 2020-10-23 01:50:04 -04:00
122 changed files with 19292 additions and 618 deletions

61
.all-contributorsrc Normal file
View File

@@ -0,0 +1,61 @@
{
"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
}

4
.github/FUNDING.yml vendored Normal file
View File

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

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe The Problem**
A clear and concise description of what the bug is.
**Expected Error / Traceback**
```
Please provide the error traceback here
```
**To Reproduce**
Please provide how you run the dashboard
**OS Information:**
- OS: [e.g. Ubuntu 18.02]
- Python Version: [e.g v3.7]
**Sample of your `.conf` file**
```
Please provide a sample of your configuration file that you are having problem with. You can replace your public key and private key to ABCD...
```

View File

@@ -0,0 +1,14 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.

70
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '30 5 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# 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
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

56
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: Docker Image Build and Analysis
on:
schedule:
- cron: "0 0 * * *" # Schedule the workflow to run daily at midnight (UTC time). Adjust the time if needed.
workflow_dispatch: # Manual run trigger
inputs:
trigger-build:
description: 'Trigger a manual build and push'
default: 'true'
jobs:
build-and-analyze:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build Docker image
id: build-image
run: |
echo "Building Docker image..."
docker build -t my-app-image:latest .
echo "Docker image built successfully."
- name: Install Docker Scout
run: |
echo "Installing Docker Scout..."
curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s --
echo "Docker Scout installed successfully."
- name: Analyze Docker image with Docker Scout
id: analyze-image
run: |
echo "Analyzing Docker image with Docker Scout..."
docker scout cves my-app-image:latest > scout-results.txt
cat scout-results.txt # Print the report to the workflow logs for easy viewing
echo "Docker Scout analysis completed."
- name: Post Comment on Issue or PR
run: |
COMMENT="**Docker Image Build and Analysis Report**\n\nThe Docker image was built and analyzed successfully.\n\n**Build Summary:**\n- Image Tag: my-app-image:latest\n\n**Analysis Report:**\n\`\`\`\n$(cat scout-results.txt)\n\`\`\`"
# Post comment using GitHub API
curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
-d "{\"body\": \"$COMMENT\"}" \
"https://api.github.com/repos/NOXCIS/WGDashboard/issues/1/comments" # Replace '1' with the issue or PR number

53
.gitignore vendored
View File

@@ -1,2 +1,51 @@
src/.vscode/sftp.json
.DS_Store
.vscode
.DS_Store
.idea
src/db
__pycache__
src/test.py
*.db
src/wg-dashboard.ini
src/static/pic.xd
*.conf
private_key.txt
public_key.txt
venv/**
log/**
release/*
src/db/wgdashboard.db
.jshintrc
node_modules/**
*/proxy.js
src/static/app/proxy.js
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.vite/*

9
.vscode/sftp.json vendored
View File

@@ -1,9 +0,0 @@
{
"name": "My Server",
"host": "38.106.21.18",
"protocol": "sftp",
"port": 22,
"username": "root",
"remotePath": "/root/wireguard-dashboard",
"uploadOnSave": true
}

31
Dockerfile Normal file
View File

@@ -0,0 +1,31 @@
# Pull from small Debian stable image.
FROM alpine:latest AS builder
LABEL maintainer="dselen@nerthus.nl"
WORKDIR /opt/wireguarddashboard/src
RUN apk update && \
apk add --no-cache sudo gcc musl-dev rust cargo linux-headers
COPY ./docker/alpine/builder.sh /opt/wireguarddashboard/src/
COPY ./docker/alpine/requirements.txt /opt/wireguarddashboard/src/
RUN chmod u+x /opt/wireguarddashboard/src/builder.sh
RUN /opt/wireguarddashboard/src/builder.sh
FROM alpine:latest
WORKDIR /opt/wireguarddashboard/src
COPY ./src /opt/wireguarddashboard/src/
COPY --from=builder /opt/wireguarddashboard/src/venv /opt/wireguarddashboard/src/venv
COPY --from=builder /opt/wireguarddashboard/src/log /opt/wireguarddashboard/src/log/
RUN apk update && \
apk add --no-cache wireguard-tools sudo && \
apk add --no-cache iptables ip6tables && \
chmod u+x /opt/wireguarddashboard/src/entrypoint.sh
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD curl -f http://localhost:10086/signin || exit 1
ENTRYPOINT ["/opt/wireguarddashboard/src/entrypoint.sh"]

601
README.md
View File

@@ -1,19 +1,586 @@
# Wireguard Dashboard
## Intro
Monitoring Wireguard is not convinient, need to login into server and type wg show. That's why this platform is being created, to view all configurations in a more straight forward way.
## Installation
**Requirement:**
- Wireguard
- Configuration files under **/etc/wireguard**
- Python 3.7
- Flask
<p align="center">
<img alt="WGDashboard" src="./src/static/img/logo.png" width="128">
</p>
<h1 align="center">WGDashboard</h1>
**Install:**
1. `git clone https://github.com/donaldzou/Wireguard-Dashboard.git`
2. `cd Wireguard-Dashboard`
3. `python3 dashboard.py`
4. Access your server! e.g (http://your_server_ip:10086)
## Example
![Index Image](https://github.com/donaldzou/Wireguard-Dashboard/raw/main/src/static/index.png)
![Conf Image](https://github.com/donaldzou/Wireguard-Dashboard/raw/main/src/static/configuration.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">
</p>
<p align="center">
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
</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>
</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
> [!TIP]
> [📹 Demo video on YouTube](https://www.youtube.com/watch?v=0mwzd5Gr2eU)
### 🎉 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!
<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)
* [Alpine Linux 3.20.2](#alpine-linux-3202)
* [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 | Alpine Linux |
|-----------|--------|--------------------------|----------|--------|------------------------|
| 20.04 LTS | 12.6 | 9.4 | 9-Stream | 40 | 3.20.2 (Under Testing) |
| 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
```
#### Alpine Linux 3.20.2
```shell
setup-interfaces -a ; \
rc-service networking --quiet start ; \
printf "https://mirrors.aliyun.com/alpine/latest-stable/main\nhttps://mirrors.aliyun.com/alpine/latest-stable/community" > /etc/apk/repositories ; \
apk update ; \
apk add wireguard-tools python3 python3-dev git iptables net-tools gcc musl-dev linux-headers sudo ; \
git clone -b v4.0-alpine-linux https://github.com/donaldzou/WGDashboard.git ; \
cd ./WGDashboard/src ; \
chmod +x ./wgd.sh ; \
./wgd.sh install
```
### 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 --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)

10
SECURITY.md Normal file
View File

@@ -0,0 +1,10 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 5.1.x | :white_check_mark: |
| 5.0.x | :x: |
| 4.0.x | :white_check_mark: |
| < 4.0 | :x: |

26
compose.yaml Normal file
View File

@@ -0,0 +1,26 @@
services:
wireguard-dashboard:
build: ./
container_name: wiregate
cap_add:
- NET_ADMIN
- SYS_MODULE
restart: unless-stopped
environment:
- wg_net=10.0.0.1/24
- wg_port=51820
volumes:
- wgd_configs:/etc/wireguard
- wgd_app:/opt/wireguarddashboard/src
ports:
- 10086:10086/tcp
- 51820:51820/udp
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
volumes:
wgd_configs:
wgd_app:

82
docker/Docker-explain.md Normal file
View File

@@ -0,0 +1,82 @@
# 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.

43
docker/alpine/builder.sh Normal file
View File

@@ -0,0 +1,43 @@
venv_python="./venv/bin/python3"
venv_gunicorn="./venv/bin/gunicorn"
pythonExecutable="python3"
_check_and_set_venv(){
VIRTUAL_ENV="./venv"
if [ ! -d $VIRTUAL_ENV ]; then
printf "[WGDashboard] Creating Python Virtual Environment under ./venv\n"
{ $pythonExecutable -m venv $VIRTUAL_ENV; } >> ./log/install.txt
fi
if ! $venv_python --version > /dev/null 2>&1
then
printf "[WGDashboard] %s Python Virtual Environment under ./venv failed to create. Halting now.\n" "$heavy_crossmark"
kill $TOP_PID
fi
source ${VIRTUAL_ENV}/bin/activate
}
build_core () {
if [ ! -d "log" ]
then
printf "[WGDashboard] Creating ./log folder\n"
mkdir "log"
fi
apk add --no-cache python3 net-tools python3-dev py3-virtualenv
_check_and_set_venv
printf "[WGDashboard] Upgrading Python Package Manage (PIP)\n"
{ date; python3 -m pip install --upgrade pip; printf "\n\n"; } >> ./log/install.txt
printf "[WGDashboard] Building Bcrypt & Psutil\n"
{ date; python3 -m pip install -r requirements.txt ; printf "\n\n"; } >> ./log/install.txt
printf "[WGDashboard] Build Successfull!\n"
printf "[WGDashboard] Clean Up Pip!\n"
{ date; rm -rf /opt/wireguarddashboard/src/venv/lib/python3.12/site-packages/pip* ; printf "\n\n"; } >> ./log/install.txt
}
build_core

View File

@@ -0,0 +1,2 @@
bcrypt
psutil

612
docs/api-documents.md Normal file
View File

@@ -0,0 +1,612 @@
# 📖 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
}
```

105
docs/changelogs.md Normal file
View File

@@ -0,0 +1,105 @@
# ⏰ 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 Normal file
View File

@@ -0,0 +1,17 @@
{
"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=="
}
}
}

5
package.json Normal file
View File

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

244
src/api.py Normal file
View File

@@ -0,0 +1,244 @@
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()

7
src/bulkAddBash.sh Normal file
View File

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

4
src/certbot.ini Normal file
View File

@@ -0,0 +1,4 @@
authenticator = standalone
noninteractive = true
agree-tos = true
rsa-key-size = 2048

File diff suppressed because it is too large Load Diff

34
src/entrypoint.sh Normal file
View File

@@ -0,0 +1,34 @@
#!/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
else
echo "No remains found, continuing."
fi
}
ensure_blocking() {
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/src/log" -mindepth 1 -maxdepth 1 -type f | read -r; then
latestErrLog=$(find /opt/wireguarddashboard/src/log -name "error_*.log" | head -n 1)
latestAccLog=$(find /opt/wireguarddashboard/src/log -name "access_*.log" | head -n 1)
tail -f "${latestErrLog}" "${latestAccLog}"
fi
# Blocking command in case of erroring. So the container does not quit.
sleep infinity
}
{ date; clean_up; printf "\n\n"; } >> ./log/install.txt
chmod u+x /opt/wireguarddashboard/src/wgd.sh
/opt/wireguarddashboard/src/wgd.sh install
/opt/wireguarddashboard/src/wgd.sh docker_start
ensure_blocking

26
src/gunicorn.conf.py Normal file
View File

@@ -0,0 +1,26 @@
import dashboard
from datetime import datetime
global sqldb, cursor, DashboardConfig, WireguardConfigurations, AllPeerJobs, JobLogger
app_host, app_port = dashboard.gunicornConfig()
date = datetime.today().strftime('%Y_%m_%d_%H_%M_%S')
def post_worker_init(worker):
dashboard.startThreads()
worker_class = 'gthread'
workers = 1
threads = 1
bind = f"{app_host}:{app_port}"
daemon = True
pidfile = './gunicorn.pid'
wsgi_app = "dashboard:app"
accesslog = f"./log/access_{date}.log"
log_level = "debug"
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)

View File

@@ -0,0 +1,13 @@
#!/bin/bash
WIREGUARD_INTERFACE=ADMINS
WIREGUARD_LAN=10.0.0.1/24
MASQUERADE_INTERFACE=eth0
CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
iptables -t nat -D POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
# Remove and delete the WIREGUARD_wg0 chain
iptables -D FORWARD -j $CHAIN_NAME
iptables -F $CHAIN_NAME
iptables -X $CHAIN_NAME

View File

@@ -0,0 +1,26 @@
#!/bin/bash
WIREGUARD_INTERFACE=ADMINS
WIREGUARD_LAN=10.0.0.1/24
MASQUERADE_INTERFACE=eth0
iptables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
# Add a WIREGUARD_wg0 chain to the FORWARD chain
CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
iptables -N $CHAIN_NAME
iptables -A FORWARD -j $CHAIN_NAME
# Accept related or established traffic
iptables -A $CHAIN_NAME -o $WIREGUARD_INTERFACE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Accept traffic from any Wireguard IP address connected to the Wireguard server
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -j ACCEPT
# Allow traffic to the local loopback interface
iptables -A $CHAIN_NAME -o lo -j ACCEPT
# Drop everything else coming through the Wireguard interface
iptables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP
# Return to FORWARD chain
iptables -A $CHAIN_NAME -j RETURN

View File

8
src/requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
bcrypt
ifcfg
psutil
pyotp
Flask
flask-cors
icmplib
gunicorn

BIN
src/static/.DS_Store vendored

Binary file not shown.

30
src/static/app/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
.vite/*

Binary file not shown.

Binary file not shown.

15
src/static/app/dist/assets/index.css vendored Normal file

File diff suppressed because one or more lines are too long

68
src/static/app/dist/assets/index.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
src/static/app/dist/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

14
src/static/app/dist/index.html vendored Normal file
View File

@@ -0,0 +1,14 @@
<!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>

13
src/static/app/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.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>
<script type="module" src="./src/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

6408
src/static/app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
{
"name": "app",
"version": "4.0.4",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build electron": "vite build && vite build --mode electron && cd ../../../../WGDashboard-Desktop && electron-builder --mac --win",
"preview": "vite preview"
},
"dependencies": {
"@vuepic/vue-datepicker": "^9.0.1",
"@vueuse/core": "^10.9.0",
"@vueuse/shared": "^10.9.0",
"animate.css": "^4.1.1",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.11.2",
"cidr-tools": "^7.0.4",
"dayjs": "^1.11.12",
"electron-builder": "^24.13.3",
"fuse.js": "^7.0.0",
"i": "^0.3.7",
"is-cidr": "^5.0.3",
"npm": "^10.5.0",
"pinia": "^2.1.7",
"qrcode": "^1.5.3",
"qrcodejs": "^1.0.0",
"uuid": "^9.0.1",
"vue": "^3.4.29",
"vue-chartjs": "^5.3.0",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"vite": "^5.0.10"
}
}

1
src/static/app/proxy.js Normal file
View File

@@ -0,0 +1 @@
export const proxy = "http://wg.local:10086/"

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

View File

@@ -0,0 +1,72 @@
<script setup>
import { RouterView } from 'vue-router'
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {computed, watch} from "vue";
const store = DashboardConfigurationStore();
store.initCrossServerConfiguration();
if (window.IS_WGDASHBOARD_DESKTOP){
store.IsElectronApp = true;
store.CrossServerConfiguration.Enable = true;
}
watch(store.CrossServerConfiguration, () => {
store.syncCrossServerConfiguration()
}, {
deep: true
});
const getActiveCrossServer = computed(() => {
if (store.ActiveServerConfiguration){
return store.CrossServerConfiguration.ServerList[store.ActiveServerConfiguration]
}
return undefined
})
</script>
<template>
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark">
<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>
<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>
</div>
</nav>
<Suspense>
<RouterView v-slot="{ Component }">
<Transition name="app" mode="out-in">
<Component :is="Component"></Component>
</Transition>
</RouterView>
</Suspense>
</template>
<style scoped>
.app-enter-active,
.app-leave-active {
transition: all 0.3s cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
.app-enter-from{
transform: translateY(20px);
opacity: 0;
}
.app-leave-to {
transform: translateY(-20px);
opacity: 0;
}
@media screen and (min-width: 768px) {
.navbarBtn{
display: none;
}
}
</style>

View File

@@ -0,0 +1,142 @@
<script>
import {fetchGet} from "@/utilities/fetch.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "allowedIPsInput",
props: {
data: Object,
saving: Boolean,
bulk: Boolean,
availableIp: undefined,
},
data(){
return {
allowedIp: [],
availableIpSearchString: "",
customAvailableIp: "",
allowedIpFormatError: false
}
},
setup(){
const store = WireguardConfigurationsStore();
const dashboardStore = DashboardConfigurationStore();
return {store, dashboardStore}
},
computed: {
searchAvailableIps(){
return this.availableIpSearchString ?
this.availableIp.filter(x =>
x.includes(this.availableIpSearchString) && !this.data.allowed_ips.includes(x)) :
this.availableIp.filter(x => !this.data.allowed_ips.includes(x))
}
},
methods: {
addAllowedIp(ip){
if(this.store.checkCIDR(ip)){
this.data.allowed_ips.push(ip);
this.customAvailableIp = ''
return true;
}
this.allowedIpFormatError = true;
this.dashboardStore.newMessage('WGDashboard', 'Allowed IP is invalid', 'danger')
return false;
}
},
watch: {
customAvailableIp(){
this.allowedIpFormatError = false;
},
availableIp(){
if (this.availableIp !== undefined && this.availableIp.length > 0){
this.addAllowedIp(this.availableIp[0])
}
}
},
mounted() {
}
}
</script>
<template>
<div :class="{inactiveField: this.bulk}">
<label for="peer_allowed_ip_textbox" class="form-label">
<small class="text-muted">Allowed IPs <code>(Required)</code></small>
</label>
<div class="d-flex gap-2 flex-wrap" :class="{'mb-2': this.data.allowed_ips.length > 0}">
<TransitionGroup name="list">
<span class="badge rounded-pill text-bg-success" v-for="(ip, index) in this.data.allowed_ips" :key="ip">
{{ip}}
<a role="button" @click="this.data.allowed_ips.splice(index, 1)">
<i class="bi bi-x-circle-fill ms-1"></i></a>
</span>
</TransitionGroup>
</div>
<div class="d-flex gap-2 align-items-center">
<div class="input-group">
<input type="text" class="form-control form-control-sm rounded-start-3"
placeholder="Enter IP Address/CIDR"
:class="{'is-invalid': this.allowedIpFormatError}"
v-model="customAvailableIp"
:disabled="bulk">
<button class="btn btn-outline-success btn-sm rounded-end-3"
:disabled="bulk || !this.customAvailableIp"
@click="this.addAllowedIp(this.customAvailableIp)"
type="button" id="button-addon2">
<i class="bi bi-plus-lg"></i>
</button>
</div>
<small class="text-muted">or</small>
<div class="dropdown flex-grow-1">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle rounded-3 w-100"
:disabled="!availableIp || bulk"
data-bs-auto-close="outside"
type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-filter-circle me-2"></i>
Pick Available IP
</button>
<ul class="dropdown-menu mt-2 shadow w-100 dropdown-menu-end rounded-3"
v-if="this.availableIp"
style="overflow-y: scroll; max-height: 270px; width: 300px !important;">
<li>
<div class="px-3 pb-2 pt-1">
<input class="form-control form-control-sm rounded-3"
v-model="this.availableIpSearchString"
placeholder="Search...">
</div>
</li>
<li v-for="ip in this.searchAvailableIps" >
<a class="dropdown-item d-flex" role="button" @click="this.addAllowedIp(ip)">
<span class="me-auto"><small>{{ip}}</small></span>
</a>
</li>
<li v-if="this.searchAvailableIps.length === 0">
<small class="px-3 text-muted">No available IP containing "{{this.availableIpSearchString}}"</small>
</li>
</ul>
</div>
</div>
</div>
</template>
<style scoped>
.list-move, /* apply transition to moving elements */
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateY(10px);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.list-leave-active {
position: absolute;
}
</style>

View File

@@ -0,0 +1,41 @@
<script>
export default {
name: "bulkAdd",
props: {
saving: Boolean,
data: Object,
availableIp: undefined
}
}
</script>
<template>
<div>
<div class="form-check form-switch ">
<input class="form-check-input"
type="checkbox" role="switch"
:disabled="!this.availableIp"
id="bulk_add" v-model="this.data.bulkAdd">
<label class="form-check-label me-2" for="bulk_add">
<small><strong>Bulk Add</strong></small>
</label>
</div>
<p :class="{'mb-0': !this.data.bulkAdd}"><small class="text-muted d-block">
By adding peers by bulk, each peer's name will be auto generated, and Allowed IP will be assign to the next available IP.
</small></p>
<div class="form-group" v-if="this.data.bulkAdd">
<input class="form-control form-control-sm rounded-3 mb-1" type="number" min="1"
:max="this.availableIp.length"
v-model="this.data.bulkAddAmount"
placeholder="How many peers you want to add?">
<small class="text-muted">
You can add up to <strong>{{this.availableIp.length}}</strong> peers
</small>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,66 @@
<script>
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "dnsInput",
props: {
data: Object,
saving: Boolean,
},
data(){
return {
error: false,
dns: JSON.parse(JSON.stringify(this.data.DNS)),
}
},
setup(){
const store = WireguardConfigurationsStore();
const dashboardStore = DashboardConfigurationStore();
return {store, dashboardStore}
},
methods:{
checkDNS(){
if(this.dns){
let i = this.dns.split(',').map(x => x.replaceAll(' ', ''));
for(let ip in i){
if (!this.store.regexCheckIP(i[ip])){
if (!this.error){
this.dashboardStore.newMessage("WGDashboard", "DNS is invalid", "danger");
}
this.error = true;
this.data.DNS = "";
return;
}
}
this.error = false;
this.data.DNS = this.dns;
}
}
},
watch: {
'dns'(){
this.checkDNS();
}
}
}
</script>
<template>
<div>
<label for="peer_DNS_textbox" class="form-label">
<small class="text-muted">DNS</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:class="{'is-invalid': this.error}"
:disabled="this.saving"
v-model="this.dns"
id="peer_DNS_textbox">
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,63 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
export default {
name: "endpointAllowedIps",
props: {
data: Object,
saving: Boolean
},
setup(){
const store = WireguardConfigurationsStore();
const dashboardStore = DashboardConfigurationStore()
return {store, dashboardStore}
},
data(){
return {
endpointAllowedIps: JSON.parse(JSON.stringify(this.data.endpoint_allowed_ip)),
error: false
}
},
methods: {
checkAllowedIP(){
let i = this.endpointAllowedIps.split(",").map(x => x.replaceAll(' ', ''));
for (let ip in i){
if (!this.store.checkCIDR(i[ip])){
if (!this.error){
this.dashboardStore.newMessage("WGDashboard", "Endpoint Allowed IP is invalid.", "danger")
}
this.data.endpoint_allowed_ip = "";
this.error = true;
return;
}
}
this.error = false;
this.data.endpoint_allowed_ip = this.endpointAllowedIps;
}
},
watch: {
'endpointAllowedIps'(){
this.checkAllowedIP();
}
}
}
</script>
<template>
<div>
<label for="peer_endpoint_allowed_ips" class="form-label">
<small class="text-muted">Endpoint Allowed IPs <code>(Required)</code></small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:class="{'is-invalid': error}"
:disabled="this.saving"
v-model="this.endpointAllowedIps"
@blur="this.checkAllowedIP()"
id="peer_endpoint_allowed_ips">
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,23 @@
<script>
export default {
name: "mtuInput",
props: {
data: Object,
saving: Boolean
}
}
</script>
<template>
<div>
<label for="peer_mtu" class="form-label"><small class="text-muted">MTU</small></label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.mtu"
id="peer_mtu">
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,26 @@
<script>
export default {
name: "nameInput",
props: {
bulk: Boolean,
data: Object,
saving: Boolean
}
}
</script>
<template>
<div :class="{inactiveField: this.bulk}">
<label for="peer_name_textbox" class="form-label">
<small class="text-muted">Name</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving || this.bulk"
v-model="this.data.name"
id="peer_name_textbox" placeholder="">
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,25 @@
<script>
export default {
name: "persistentKeepAliveInput",
props: {
data: Object,
saving: Boolean
}
}
</script>
<template>
<div>
<label for="peer_keep_alive" class="form-label">
<small class="text-muted">Persistent Keepalive</small>
</label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.keepalive"
id="peer_keep_alive">
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,25 @@
<script>
export default {
name: "presharedKeyInput",
props: {
data: Object,
saving: Boolean
}
}
</script>
<template>
<div>
<label for="peer_preshared_key_textbox" class="form-label">
<small class="text-muted">Pre-Shared Key</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.preshared_key"
id="peer_preshared_key_textbox">
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,120 @@
<script>
import "@/utilities/wireguard.js"
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "privatePublicKeyInput",
props: {
data: Object,
saving: Boolean,
bulk: Boolean
},
setup(){
const dashboardStore = DashboardConfigurationStore();
return {dashboardStore}
},
data(){
return {
keypair: {
publicKey: "",
privateKey: "",
presharedKey: ""
},
editKey: false,
error: false
}
},
methods: {
genKeyPair(){
this.editKey = false;
this.keypair = window.wireguard.generateKeypair();
this.data.private_key = this.keypair.privateKey;
this.data.public_key = this.keypair.publicKey;
},
testKey(key){
const reg = /^[A-Za-z0-9+/]{43}=?=?$/;
return reg.test(key)
},
checkMatching(){
try{
if(this.keypair.privateKey){
if(this.testKey(this.keypair.privateKey)){
this.keypair.publicKey = window.wireguard.generatePublicKey(this.keypair.privateKey)
if (window.wireguard.generatePublicKey(this.keypair.privateKey)
!== this.keypair.publicKey){
this.error = true;
this.dashboardStore.newMessage("WGDashboard", "Private Key and Public Key does not match.", "danger");
}else{
this.data.private_key = this.keypair.privateKey
this.data.public_key = this.keypair.publicKey
}
}
}
}catch (e){
this.error = true;
this.data.private_key = "";
this.data.public_key = "";
}
}
},
mounted() {
this.genKeyPair();
},
watch: {
keypair: {
deep: true,
handler(){
this.error = false;
this.checkMatching();
}
}
}
}
</script>
<template>
<div class="d-flex gap-2 flex-column" :class="{inactiveField: this.bulk}">
<div>
<label for="peer_private_key_textbox" class="form-label">
<small class="text-muted">Private Key <code>(Required for QR Code and Download)</code></small>
</label>
<div class="input-group">
<input type="text" class="form-control form-control-sm rounded-start-3"
v-model="this.keypair.privateKey"
:disabled="!this.editKey || this.bulk"
:class="{'is-invalid': this.error}"
@blur="this.checkMatching()"
id="peer_private_key_textbox">
<button class="btn btn-outline-info btn-sm rounded-end-3"
@click="this.genKeyPair()"
:disabled="this.bulk"
type="button" id="button-addon2">
<i class="bi bi-arrow-repeat"></i> </button>
</div>
</div>
<div>
<div class="d-flex">
<label for="public_key" class="form-label">
<small class="text-muted">Public Key <code>(Required)</code></small>
</label>
<div class="form-check form-switch ms-auto">
<input class="form-check-input" type="checkbox" role="switch"
:disabled="this.bulk"
id="enablePublicKeyEdit" v-model="this.editKey">
<label class="form-check-label" for="enablePublicKeyEdit">
<small>Edit</small>
</label>
</div>
</div>
<input class="form-control-sm form-control rounded-3"
:class="{'is-invalid': this.error}"
v-model="this.keypair.publicKey"
@blur="this.checkMatching()"
:disabled="!this.editKey || this.bulk"
type="text" id="public_key">
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,125 @@
<script>
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
import "animate.css"
import PeerSettingsDropdown from "@/components/configurationComponents/peerSettingsDropdown.vue";
export default {
name: "peer",
components: {PeerSettingsDropdown},
props: {
Peer: Object
},
data(){
return {
}
},
setup(){
const target = ref(null);
const subMenuOpened = ref(false)
onClickOutside(target, event => {
subMenuOpened.value = false;
});
return {target, subMenuOpened}
},
computed: {
getLatestHandshake(){
if (this.Peer.latest_handshake.includes(",")){
return this.Peer.latest_handshake.split(",")[0]
}
return this.Peer.latest_handshake;
}
}
}
</script>
<template>
<div class="card shadow-sm rounded-3 peerCard bg-transparent"
:class="{'border-warning': Peer.restricted}">
<div>
<div v-if="!Peer.restricted" class="card-header bg-transparent d-flex align-items-center gap-2 border-0">
<div class="dot ms-0" :class="{active: Peer.status === 'running'}"></div>
<div style="font-size: 0.8rem" class="ms-auto d-flex gap-2">
<span class="text-primary">
<i class="bi bi-arrow-down"></i><strong>
{{(Peer.cumu_receive + Peer.total_receive).toFixed(4)}}</strong> GB
</span>
<span class="text-success">
<i class="bi bi-arrow-up"></i><strong>
{{(Peer.cumu_sent + Peer.total_sent).toFixed(4)}}</strong> GB
</span>
<span class="text-secondary" v-if="Peer.latest_handshake !== 'No Handshake'">
<i class="bi bi-arrows-angle-contract"></i>
{{getLatestHandshake}} ago
</span>
</div>
</div>
<div v-else class="border-0 card-header bg-transparent text-warning fw-bold"
style="font-size: 0.8rem">
<i class="bi-lock-fill me-2"></i>
Access Restricted
</div>
</div>
<div class="card-body pt-1" style="font-size: 0.9rem">
<h6>
{{Peer.name ? Peer.name : 'Untitled Peer'}}
</h6>
<div class="mb-2">
<small class="text-muted">Public Key</small>
<p class="mb-0"><samp>{{Peer.id}}</samp></p>
</div>
<div class="d-flex align-items-end">
<div>
<small class="text-muted">Allowed IP</small>
<p class="mb-0"><samp>{{Peer.allowed_ip}}</samp></p>
</div>
<div class="ms-auto px-2 rounded-3 subMenuBtn"
:class="{active: this.subMenuOpened}"
>
<a role="button" class="text-body"
@click="this.subMenuOpened = true">
<h5 class="mb-0"><i class="bi bi-three-dots"></i></h5>
</a>
<Transition name="slide-fade">
<PeerSettingsDropdown
@qrcode="(file) => this.$emit('qrcode', file)"
@setting="this.$emit('setting')"
@jobs="this.$emit('jobs')"
@refresh="this.$emit('refresh')"
@share="this.$emit('share')"
:Peer="Peer"
v-if="this.subMenuOpened"
ref="target"
></PeerSettingsDropdown>
</Transition>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.slide-fade-leave-active, .slide-fade-enter-active{
transition: all 0.2s cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateY(20px);
opacity: 0;
filter: blur(3px);
}
.subMenuBtn.active{
background-color: #ffffff20;
}
.peerCard{
transition: box-shadow 0.1s cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
.peerCard:hover{
box-shadow: var(--bs-box-shadow) !important;
}
</style>

View File

@@ -0,0 +1,163 @@
<script>
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import NameInput from "@/components/configurationComponents/newPeersComponents/nameInput.vue";
import PrivatePublicKeyInput from "@/components/configurationComponents/newPeersComponents/privatePublicKeyInput.vue";
import AllowedIPsInput from "@/components/configurationComponents/newPeersComponents/allowedIPsInput.vue";
import DnsInput from "@/components/configurationComponents/newPeersComponents/dnsInput.vue";
import EndpointAllowedIps from "@/components/configurationComponents/newPeersComponents/endpointAllowedIps.vue";
import PresharedKeyInput from "@/components/configurationComponents/newPeersComponents/presharedKeyInput.vue";
import MtuInput from "@/components/configurationComponents/newPeersComponents/mtuInput.vue";
import PersistentKeepAliveInput
from "@/components/configurationComponents/newPeersComponents/persistentKeepAliveInput.vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import BulkAdd from "@/components/configurationComponents/newPeersComponents/bulkAdd.vue";
export default {
name: "peerCreate",
components: {
BulkAdd,
PersistentKeepAliveInput,
MtuInput,
PresharedKeyInput, EndpointAllowedIps, DnsInput, AllowedIPsInput, PrivatePublicKeyInput, NameInput},
data(){
return{
data: {
bulkAdd: false,
bulkAddAmount: "",
name: "",
allowed_ips: [],
private_key: "",
public_key: "",
DNS: this.dashboardStore.Configuration.Peers.peer_global_dns,
endpoint_allowed_ip: this.dashboardStore.Configuration.Peers.peer_endpoint_allowed_ip,
keepalive: parseInt(this.dashboardStore.Configuration.Peers.peer_keep_alive),
mtu: parseInt(this.dashboardStore.Configuration.Peers.peer_mtu),
preshared_key: ""
},
availableIp: undefined,
availableIpSearchString: "",
saving: false,
allowedIpDropdown: undefined
}
},
mounted() {
fetchGet("/api/getAvailableIPs/" + this.$route.params.id, {}, (res) => {
if (res.status){
this.availableIp = res.data;
}
})
},
setup(){
const store = WireguardConfigurationsStore();
const dashboardStore = DashboardConfigurationStore();
return {store, dashboardStore}
},
methods: {
peerCreate(){
this.saving = true
fetchPost("/api/addPeers/" + this.$route.params.id, this.data, (res) => {
if (res.status){
this.$router.push(`/configuration/${this.$route.params.id}/peers`)
this.dashboardStore.newMessage("Server", "Peer create successfully", "success")
}else{
this.dashboardStore.newMessage("Server", res.message, "danger")
}
this.saving = false;
})
}
},
computed:{
allRequireFieldsFilled(){
let status = true;
if (this.data.bulkAdd){
if(this.data.bulkAddAmount.length === 0 || this.data.bulkAddAmount > this.availableIp.length){
status = false;
}
}else{
let requireFields =
["allowed_ips", "private_key", "public_key", "endpoint_allowed_ip", "keepalive", "mtu"]
requireFields.forEach(x => {
if (this.data[x].length === 0) status = false;
});
}
return status;
}
},
watch: {
bulkAdd(newVal){
if(!newVal){
this.data.bulkAddAmount = "";
}
},
'data.bulkAddAmount'(){
if (this.data.bulkAddAmount > this.availableIp.length){
this.data.bulkAddAmount = this.availableIp.length;
}
}
}
}
</script>
<template>
<div class="container">
<div class="mb-4">
<RouterLink to="peers" is="div" class="d-flex align-items-center gap-4 text-decoration-none">
<h3 class="mb-0 text-body">
<i class="bi bi-chevron-left"></i>
</h3>
<h3 class="text-body mb-0">Add Peers</h3>
</RouterLink>
</div>
<div class="d-flex flex-column gap-2">
<BulkAdd :saving="saving" :data="this.data" :availableIp="this.availableIp"></BulkAdd>
<hr class="mb-0 mt-2">
<NameInput :saving="saving" :data="this.data" v-if="!this.data.bulkAdd"></NameInput>
<PrivatePublicKeyInput :saving="saving" :data="data" v-if="!this.data.bulkAdd"></PrivatePublicKeyInput>
<AllowedIPsInput :availableIp="this.availableIp" :saving="saving" :data="data" v-if="!this.data.bulkAdd"></AllowedIPsInput>
<EndpointAllowedIps :saving="saving" :data="data"></EndpointAllowedIps>
<DnsInput :saving="saving" :data="data"></DnsInput>
<hr class="mb-0 mt-2">
<div class="row">
<div class="col-sm" v-if="!this.data.bulkAdd">
<PresharedKeyInput :saving="saving" :data="data" :bulk="this.data.bulkAdd"></PresharedKeyInput>
</div>
<div class="col-sm">
<MtuInput :saving="saving" :data="data"></MtuInput>
</div>
<div class="col-sm">
<PersistentKeepAliveInput :saving="saving" :data="data"></PersistentKeepAliveInput>
</div>
</div>
<div class="d-flex mt-2">
<button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow"
:disabled="!this.allRequireFieldsFilled || this.saving"
@click="this.peerCreate()"
>
<i class="bi bi-plus-circle-fill me-2" v-if="!this.saving"></i>
{{this.saving ? 'Saving...': 'Add'}}
</button>
</div>
</div>
</div>
</template>
<style scoped>
.peerSettingContainer {
background-color: #00000060;
z-index: 9998;
}
div{
transition: 0.2s ease-in-out;
}
.inactiveField{
opacity: 0.4;
}
.card{
max-height: 100%;
}
</style>

View File

@@ -0,0 +1,109 @@
<script>
import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJobsComponents/scheduleDropdown.vue";
import SchedulePeerJob from "@/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {v4} from "uuid";
export default {
name: "peerJobs",
setup(){
const store = WireguardConfigurationsStore();
return {store}
},
props:{
selectedPeer: Object
},
components:{
SchedulePeerJob,
ScheduleDropdown,
},
data(){
return {
}
},
methods:{
deleteJob(j){
this.selectedPeer.jobs = this.selectedPeer.jobs.filter(x => x.JobID !== j.JobID);
},
addJob(){
this.selectedPeer.jobs.unshift(JSON.parse(JSON.stringify({
JobID: v4().toString(),
Configuration: this.selectedPeer.configuration.Name,
Peer: this.selectedPeer.id,
Field: this.store.PeerScheduleJobs.dropdowns.Field[0].value,
Operator: this.store.PeerScheduleJobs.dropdowns.Operator[0].value,
Value: "",
CreationDate: "",
ExpireDate: "",
Action: this.store.PeerScheduleJobs.dropdowns.Action[0].value
}))
)
}
},
}
</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">
<div class="card rounded-3 shadow" 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 fw-normal">Schedule Jobs
<strong></strong>
</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4 pt-2 position-relative">
<div class="d-flex align-items-center mb-3">
<button class="btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow"
@click="this.addJob()">
<i class="bi bi-plus-lg me-2"></i> Job
</button>
</div>
<TransitionGroup name="schedulePeerJobTransition" tag="div" class="position-relative">
<SchedulePeerJob
@refresh="this.$emit('refresh')"
@delete="this.deleteJob(job)"
:dropdowns="this.store.PeerScheduleJobs.dropdowns"
:key="job.JobID"
:pjob="job" v-for="(job, index) in this.selectedPeer.jobs">
</SchedulePeerJob>
<div class="card shadow-sm" key="none"
style="height: 153px"
v-if="this.selectedPeer.jobs.length === 0">
<div class="card-body text-muted text-center d-flex">
<h6 class="m-auto">This peer does not have any job yet.</h6>
</div>
</div>
</TransitionGroup>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.schedulePeerJobTransition-move, /* apply transition to moving elements */
.schedulePeerJobTransition-enter-active,
.schedulePeerJobTransition-leave-active {
transition: all 0.4s cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
.schedulePeerJobTransition-enter-from,
.schedulePeerJobTransition-leave-to {
opacity: 0;
transform: scale(0.9);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.schedulePeerJobTransition-leave-active {
position: absolute;
width: 100%;
}
</style>

View File

@@ -0,0 +1,86 @@
<script>
import SchedulePeerJob from "@/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {v4} from "uuid";
export default {
name: "peerJobsAllModal",
setup(){
const store = WireguardConfigurationsStore();
return {store}
},
components: {SchedulePeerJob},
props: {
configurationPeers: Array[Object]
},
methods:{
getuuid(){
return v4();
}
},
computed:{
getAllJobs(){
return this.configurationPeers.filter(x => x.jobs.length > 0)
}
}
}
</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">
<div class="card rounded-3 shadow" 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 fw-normal">All Active Jobs
</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4 pt-2 ">
<div class="accordion" id="peerJobsLogsModalAccordion" v-if="this.getAllJobs.length > 0">
<div class="accordion-item" v-for="(p, index) in this.getAllJobs" :key="p.id">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
:data-bs-target="'#collapse_' + index">
<small>
<strong>
<span v-if="p.name">
{{p.name}} &#x2022;
</span>
<samp class="text-muted">{{p.id}}</samp>
</strong>
</small>
</button>
</h2>
<div :id="'collapse_' + index" class="accordion-collapse collapse"
data-bs-parent="#peerJobsLogsModalAccordion">
<div class="accordion-body">
<SchedulePeerJob
@delete="this.$emit('refresh')"
@refresh="this.$emit('refresh')"
:dropdowns="this.store.PeerScheduleJobs.dropdowns"
:viewOnly="true"
:key="job.JobID"
:pjob="job" v-for="job in p.jobs">
</SchedulePeerJob>
</div>
</div>
</div>
</div>
<div class="card shadow-sm"
style="height: 153px"
v-else>
<div class="card-body text-muted text-center d-flex">
<h6 class="m-auto">No active job at the moment.</h6>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,162 @@
<script>
import dayjs from "dayjs";
import {fetchGet} from "@/utilities/fetch.js";
export default {
name: "peerJobsLogsModal",
props: {
configurationInfo: Object
},
data(){
return {
dataLoading: true,
data: [],
logFetchTime: undefined,
showLogID: false,
showJobID: true,
showSuccessJob: true,
showFailedJob: true,
showLogAmount: 10
}
},
async mounted(){
await this.fetchLog();
},
methods: {
async fetchLog(){
this.dataLoading = true;
await fetchGet(`/api/getPeerScheduleJobLogs/${this.configurationInfo.Name}`, {}, (res) => {
this.data = res.data;
this.logFetchTime = dayjs().format("YYYY-MM-DD HH:mm:ss")
this.dataLoading = false;
});
}
},
computed: {
getLogs(){
return this.data
.filter(x => {
return (this.showSuccessJob && x.Status === "1") || (this.showFailedJob && x.Status === "0")
})
},
showLogs(){
return this.getLogs.slice(0, this.showLogAmount);
}
}
}
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
<div class="container-fluid d-flex h-100 w-100">
<div class="m-auto mt-0 modal-dialog-centered dashboardModal" style="width: 100%">
<div class="card rounded-3 shadow w-100" >
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h4 class="mb-0">Jobs Logs</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4 pt-2">
<div v-if="!this.dataLoading">
<p>Updated at: {{this.logFetchTime}}</p>
<div class="mb-2 d-flex gap-3">
<button @click="this.fetchLog()"
class="btn btn-sm rounded-3 shadow-sm
text-info-emphasis bg-info-subtle border-1 border-info-subtle me-1">
<i class="bi bi-arrow-clockwise me-2"></i>
Refresh
</button>
<div class="d-flex gap-3 align-items-center">
<span class="text-muted">Filter</span>
<div class="form-check">
<input class="form-check-input" type="checkbox" v-model="this.showSuccessJob"
id="jobLogsShowSuccessCheck">
<label class="form-check-label" for="jobLogsShowSuccessCheck">
<span class="badge text-success-emphasis bg-success-subtle">Success</span>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" v-model="this.showFailedJob"
id="jobLogsShowFailedCheck">
<label class="form-check-label" for="jobLogsShowFailedCheck">
<span class="badge text-danger-emphasis bg-danger-subtle">Failed</span>
</label>
</div>
</div>
<div class="d-flex gap-3 align-items-center ms-auto">
<span class="text-muted">Display</span>
<div class="form-check">
<input class="form-check-input" type="checkbox"
v-model="showJobID"
id="jobLogsShowJobIDCheck">
<label class="form-check-label" for="jobLogsShowJobIDCheck">
Job ID
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
v-model="showLogID"
id="jobLogsShowLogIDCheck">
<label class="form-check-label" for="jobLogsShowLogIDCheck">
Log ID
</label>
</div>
</div>
</div>
<table class="table">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col" v-if="showLogID">Log ID</th>
<th scope="col" v-if="showJobID">Job ID</th>
<th scope="col">Status</th>
<th scope="col">Message</th>
</tr>
</thead>
<tbody>
<tr v-for="log in this.showLogs" style="font-size: 0.875rem">
<th scope="row">{{log.LogDate}}</th>
<td v-if="showLogID"><samp class="text-muted">{{log.LogID}}</samp></td>
<td v-if="showJobID"><samp class="text-muted">{{log.JobID}}</samp></td>
<td>
<span class="badge" :class="[log.Status === '1' ? 'text-success-emphasis bg-success-subtle':'text-danger-emphasis bg-danger-subtle']">
{{log.Status === "1" ? 'Success': 'Failed'}}
</span>
</td>
<td>{{log.Message}}</td>
</tr>
</tbody>
</table>
<div class="d-flex gap-2">
<button v-if="this.getLogs.length > this.showLogAmount"
@click="this.showLogAmount += 20"
class="btn btn-sm rounded-3 shadow-sm
text-primary-emphasis bg-primary-subtle border-1 border-primary-subtle">
<i class="bi bi-chevron-down me-2"></i>
Show More
</button>
<button v-if="this.showLogAmount > 20"
@click="this.showLogAmount = 20"
class="btn btn-sm rounded-3 shadow-sm
text-primary-emphasis bg-primary-subtle border-1 border-primary-subtle">
<i class="bi bi-chevron-up me-2"></i>
Collapse
</button>
</div>
</div>
<div class="d-flex align-items-center flex-column" v-else>
<div class="spinner-border text-body" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,625 @@
<script>
import PeerSearch from "@/components/configurationComponents/peerSearch.vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {fetchGet} from "@/utilities/fetch.js";
import Peer from "@/components/configurationComponents/peer.vue";
import { Line, Bar } from 'vue-chartjs'
import Fuse from "fuse.js";
import {
Chart,
ArcElement,
LineElement,
BarElement,
PointElement,
BarController,
BubbleController,
DoughnutController,
LineController,
PieController,
PolarAreaController,
RadarController,
ScatterController,
CategoryScale,
LinearScale,
LogarithmicScale,
RadialLinearScale,
TimeScale,
TimeSeriesScale,
Decimation,
Filler,
Legend,
Title,
Tooltip
} from 'chart.js';
import dayjs from "dayjs";
import PeerSettings from "@/components/configurationComponents/peerSettings.vue";
import PeerQRCode from "@/components/configurationComponents/peerQRCode.vue";
import PeerCreate from "@/components/configurationComponents/peerCreate.vue";
import PeerJobs from "@/components/configurationComponents/peerJobs.vue";
import PeerJobsAllModal from "@/components/configurationComponents/peerJobsAllModal.vue";
import PeerJobsLogsModal from "@/components/configurationComponents/peerJobsLogsModal.vue";
import {ref} from "vue";
import PeerShareLinkModal from "@/components/configurationComponents/peerShareLinkModal.vue";
Chart.register(
ArcElement,
LineElement,
BarElement,
PointElement,
BarController,
BubbleController,
DoughnutController,
LineController,
PieController,
PolarAreaController,
RadarController,
ScatterController,
CategoryScale,
LinearScale,
LogarithmicScale,
RadialLinearScale,
TimeScale,
TimeSeriesScale,
Decimation,
Filler,
Legend,
Title,
Tooltip
);
export default {
name: "peerList",
components: {
PeerShareLinkModal,
PeerJobsLogsModal,
PeerJobsAllModal, PeerJobs, PeerCreate, PeerQRCode, PeerSettings, PeerSearch, Peer, Line, Bar},
setup(){
const dashboardConfigurationStore = DashboardConfigurationStore();
const wireguardConfigurationStore = WireguardConfigurationsStore();
const interval = ref(undefined)
return {dashboardConfigurationStore, wireguardConfigurationStore, interval}
},
data(){
return {
configurationToggling: false,
loading: false,
error: null,
configurationInfo: [],
configurationPeers: [],
historyDataSentDifference: [],
historyDataReceivedDifference: [],
historySentData: {
labels: [],
datasets: [
{
label: 'Data Sent',
data: [],
fill: false,
borderColor: '#198754',
tension: 0
},
],
},
historyReceiveData: {
labels: [],
datasets: [
{
label: 'Data Received',
data: [],
fill: false,
borderColor: '#0d6efd',
tension: 0
},
],
},
peerSetting: {
modalOpen: false,
selectedPeer: undefined
},
peerScheduleJobs:{
modalOpen: false,
selectedPeer: undefined
},
peerQRCode: {
modalOpen: false,
peerConfigData: undefined
},
peerCreate: {
modalOpen: false
},
peerScheduleJobsAll: {
modalOpen: false
},
peerScheduleJobsLogs: {
modalOpen: false
},
peerShare:{
modalOpen: false,
selectedPeer: undefined
}
}
},
mounted() {
},
watch: {
'$route': {
immediate: true,
handler(){
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
this.loading = true;
let id = this.$route.params.id;
this.configurationInfo = [];
this.configurationPeers = [];
if (id){
this.getPeers(id)
this.setPeerInterval();
}
}
},
'dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval'(){
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
this.setPeerInterval();
}
},
beforeRouteLeave(){
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
},
methods:{
toggle(){
this.configurationToggling = true;
fetchGet("/api/toggleWireguardConfiguration/", {
configurationName: this.configurationInfo.Name
}, (res) => {
if (res.status){
this.dashboardConfigurationStore.newMessage("Server",
`${this.configurationInfo.Name} is
${res.data ? 'is on':'is off'}`, "Success")
}else{
this.dashboardConfigurationStore.newMessage("Server",
res.message, 'danger')
}
this.configurationInfo.Status = res.data
this.configurationToggling = false;
})
},
getPeers(id = this.$route.params.id){
fetchGet("/api/getWireguardConfigurationInfo",
{
configurationName: id
}, (res) => {
this.configurationInfo = res.data.configurationInfo;
this.configurationPeers = res.data.configurationPeers;
this.configurationPeers.forEach(x => {
x.restricted = false;
})
res.data.configurationRestrictedPeers.forEach(x => {
x.restricted = true;
this.configurationPeers.push(x)
})
this.loading = false;
if (this.configurationPeers.length > 0){
const sent = this.configurationPeers.map(x => x.total_sent + x.cumu_sent)
.reduce((x,y) => x + y).toFixed(4);
const receive = this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((x,y) => x + y).toFixed(4);
if (
this.historyDataSentDifference[this.historyDataSentDifference.length - 1] !== sent
){
if (this.historyDataSentDifference.length > 0){
this.historySentData = {
labels: [...this.historySentData.labels, dayjs().format("HH:mm:ss A")],
datasets: [
{
label: 'Data Sent',
data: [...this.historySentData.datasets[0].data,
((sent - this.historyDataSentDifference[this.historyDataSentDifference.length - 1])*1000)
.toFixed(4)],
fill: false,
borderColor: '#198754',
tension: 0
}
],
}
}
this.historyDataSentDifference.push(sent)
}
if (
this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1] !== receive
){
if (this.historyDataReceivedDifference.length > 0){
this.historyReceiveData = {
labels: [...this.historyReceiveData.labels, dayjs().format("HH:mm:ss A")],
datasets: [
{
label: 'Data Received',
data: [...this.historyReceiveData.datasets[0].data,
((receive - this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1])*1000)
.toFixed(4)],
fill: false,
borderColor: '#0d6efd',
tension: 0
}
],
}
}
this.historyDataReceivedDifference.push(receive)
}
}
});
},
setPeerInterval(){
this.dashboardConfigurationStore.Peers.RefreshInterval = setInterval(() => {
this.getPeers()
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
},
},
computed: {
configurationSummary(){
const k = {
connectedPeers: this.configurationPeers.filter(x => x.status === "running").length,
totalUsage: this.configurationPeers.length > 0 ?
this.configurationPeers.filter(x => !x.restricted)
.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b, 0).toFixed(4) : 0,
totalReceive: this.configurationPeers.length > 0 ?
this.configurationPeers.filter(x => !x.restricted)
.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b, 0).toFixed(4) : 0,
totalSent: this.configurationPeers.length > 0 ?
this.configurationPeers.filter(x => !x.restricted)
.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b, 0).toFixed(4) : 0
}
return k
},
receiveData(){
return this.historyReceiveData
},
sentData(){
return this.historySentData
},
individualDataUsage(){
return {
labels: this.configurationPeers.map(x => {
if (x.name) return x.name
return `Untitled Peer - ${x.id}`
}),
datasets: [{
label: 'Total Data Usage',
data: this.configurationPeers.map(x => x.cumu_data + x.total_data),
backgroundColor: this.configurationPeers.map(x => `#0dcaf0`),
tooltip: {
callbacks: {
label: (tooltipItem) => {
return `${tooltipItem.formattedValue} GB`
}
}
}
}]
}
},
individualDataUsageChartOption(){
return {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
ticks: {
display: false,
},
grid: {
display: false
},
},
y:{
ticks: {
callback: (val, index) => {
return `${val} GB`
}
},
grid: {
display: false
},
}
}
}
},
chartOptions() {
return {
responsive: true,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: (tooltipItem) => {
return `${tooltipItem.formattedValue} MB/s`
}
}
}
},
scales: {
x: {
ticks: {
display: false,
},
grid: {
display: false
},
},
y:{
ticks: {
callback: (val, index) => {
return `${val} MB/s`
}
},
grid: {
display: false
},
}
}
}
},
searchPeers(){
const fuse = new Fuse(this.configurationPeers, {
keys: ["name", "id", "allowed_ip"]
});
const result = this.wireguardConfigurationStore.searchString ?
fuse.search(this.wireguardConfigurationStore.searchString).map(x => x.item) : this.configurationPeers;
if (this.dashboardConfigurationStore.Configuration.Server.dashboard_sort === "restricted"){
return result.slice().sort((a, b) => {
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
< b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] ){
return 1;
}
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
return -1;
}
return 0;
});
}
return result.slice().sort((a, b) => {
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
< b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort] ){
return -1;
}
if ( a[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]
> b[this.dashboardConfigurationStore.Configuration.Server.dashboard_sort]){
return 1;
}
return 0;
});
}
}
}
</script>
<template>
<div v-if="!this.loading" class="container-md">
<div class="d-flex align-items-center">
<div>
<small CLASS="text-muted">CONFIGURATION</small>
<div class="d-flex align-items-center gap-3">
<h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1>
</div>
</div>
<div class="card rounded-3 bg-transparent shadow-sm ms-auto">
<div class="card-body py-2 d-flex align-items-center">
<div>
<p class="mb-0 text-muted"><small>Status</small></p>
<div class="form-check form-switch ms-auto">
<label class="form-check-label" style="cursor: pointer" :for="'switch' + this.configurationInfo.id">
{{this.configurationToggling ? 'Turning ':''}}
{{this.configurationInfo.Status ? "On":"Off"}}
<span v-if="this.configurationToggling"
class="spinner-border spinner-border-sm" aria-hidden="true"></span>
</label>
<input class="form-check-input"
style="cursor: pointer"
:disabled="this.configurationToggling"
type="checkbox" role="switch" :id="'switch' + this.configurationInfo.id"
@change="this.toggle()"
v-model="this.configurationInfo.Status"
>
</div>
</div>
<div class="dot ms-5" :class="{active: this.configurationInfo.Status}"></div>
</div>
</div>
</div>
<div class="row mt-3 gy-2 gx-2 mb-2">
<div class="col-6 col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body py-2">
<p class="mb-0 text-muted"><small>Address</small></p>
{{this.configurationInfo.Address}}
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body py-2">
<p class="mb-0 text-muted"><small>Listen Port</small></p>
{{this.configurationInfo.ListenPort}}
</div>
</div>
</div>
<div style="word-break: break-all" class="col-12 col-lg-6">
<div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body py-2">
<p class="mb-0 text-muted"><small>Public Key</small></p>
<samp>{{this.configurationInfo.PublicKey}}</samp>
</div>
</div>
</div>
</div>
<div class="row gx-2 gy-2 mb-2">
<div class="col-6 col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body d-flex">
<div>
<p class="mb-0 text-muted"><small>Connected Peers</small></p>
<strong class="h4">{{configurationSummary.connectedPeers}}</strong>
</div>
<i class="bi bi-ethernet ms-auto h2 text-muted"></i>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body d-flex">
<div>
<p class="mb-0 text-muted"><small>Total Usage</small></p>
<strong class="h4">{{configurationSummary.totalUsage}} GB</strong>
</div>
<i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body d-flex">
<div>
<p class="mb-0 text-muted"><small>Total Received</small></p>
<strong class="h4 text-primary">{{configurationSummary.totalReceive}} GB</strong>
</div>
<i class="bi bi-arrow-down ms-auto h2 text-muted"></i>
</div>
</div>
</div>
<div class="col-6 col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body d-flex">
<div>
<p class="mb-0 text-muted"><small>Total Sent</small></p>
<strong class="h4 text-success">{{configurationSummary.totalSent}} GB</strong>
</div>
<i class="bi bi-arrow-up ms-auto h2 text-muted"></i>
</div>
</div>
</div>
</div>
<div class="row gx-2 gy-2 mb-3">
<div class="col-12 col-lg-6">
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
<div class="card-header bg-transparent border-0">
<small class="text-muted">Peers Total Data Usage</small></div>
<div class="card-body pt-1">
<Bar
:data="individualDataUsage"
:options="individualDataUsageChartOption"
style="width: 100%; height: 200px; max-height: 200px"></Bar>
</div>
</div>
</div>
<div class="col-sm col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Received Data Usage</small></div>
<div class="card-body pt-1">
<Line
:options="chartOptions"
:data="receiveData"
style="width: 100%; height: 200px; max-height: 200px"
></Line>
</div>
</div>
</div>
<div class="col-sm col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Sent Data Usage</small></div>
<div class="card-body pt-1">
<Line
:options="chartOptions"
:data="sentData"
style="width: 100%; height: 200px; max-height: 200px"
></Line>
</div>
</div>
</div>
</div>
<div class="mb-3">
<PeerSearch
@jobsAll="this.peerScheduleJobsAll.modalOpen = true"
@jobLogs="this.peerScheduleJobsLogs.modalOpen = true"
:configuration="this.configurationInfo"></PeerSearch>
<TransitionGroup name="list" tag="div" class="row gx-2 gy-2 z-0">
<div class="col-12 col-lg-6 col-xl-4"
:key="peer.id"
v-for="peer in this.searchPeers">
<Peer :Peer="peer"
@share="this.peerShare.selectedPeer = peer.id; this.peerShare.modalOpen = true;"
@refresh="this.getPeers()"
@jobs="peerScheduleJobs.modalOpen = true; peerScheduleJobs.selectedPeer = this.configurationPeers.find(x => x.id === peer.id)"
@setting="peerSetting.modalOpen = true; peerSetting.selectedPeer = this.configurationPeers.find(x => x.id === peer.id)"
@qrcode="(file) => {this.peerQRCode.peerConfigData = file; this.peerQRCode.modalOpen = true;}"
></Peer>
</div>
</TransitionGroup>
</div>
<Transition name="zoom">
<PeerSettings v-if="this.peerSetting.modalOpen"
key="settings"
:selectedPeer="this.peerSetting.selectedPeer"
@refresh="this.getPeers()"
@close="this.peerSetting.modalOpen = false">
</PeerSettings>
</Transition>
<Transition name="zoom">
<PeerQRCode :peerConfigData="this.peerQRCode.peerConfigData"
key="qrcode"
@close="this.peerQRCode.modalOpen = false"
v-if="peerQRCode.modalOpen"></PeerQRCode>
</Transition>
<Transition name="zoom">
<PeerJobs
@refresh="this.getPeers()"
v-if="this.peerScheduleJobs.modalOpen"
:selectedPeer="this.peerScheduleJobs.selectedPeer"
@close="this.peerScheduleJobs.modalOpen = false">
</PeerJobs>
</Transition>
<Transition name="zoom">
<PeerJobsAllModal
v-if="this.peerScheduleJobsAll.modalOpen"
@refresh="this.getPeers()"
@close="this.peerScheduleJobsAll.modalOpen = false"
:configurationPeers="this.configurationPeers"
>
</PeerJobsAllModal>
</Transition>
<Transition name="zoom">
<PeerJobsLogsModal v-if="this.peerScheduleJobsLogs.modalOpen"
@close="this.peerScheduleJobsLogs.modalOpen = false"
:configurationInfo="this.configurationInfo"
>
</PeerJobsLogsModal>
</Transition>
<Transition name="zoom">
<PeerShareLinkModal
v-if="this.peerShare.modalOpen"
@close="this.peerShare.modalOpen = false; this.peerShare.selectedPeer = undefined;"
:peer="this.configurationPeers.find(x => x.id === this.peerShare.selectedPeer)"></PeerShareLinkModal>
</Transition>
</div>
</template>
<style scoped>
.peerNav .nav-link{
&.active{
//background: linear-gradient(var(--degree), var(--brandColor1) var(--distance2), var(--brandColor2) 100%);
//color: white;
background-color: #efefef;
}
}
</style>

View File

@@ -0,0 +1,35 @@
<script>
import QRCode from "qrcode";
export default {
name: "peerQRCode",
props: {
peerConfigData: String
},
mounted() {
QRCode.toCanvas(document.querySelector("#qrcode"), this.peerConfigData , (error) => {
if (error) console.error(error)
})
}
}
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0">
<div class="container d-flex h-100 w-100">
<div class="m-auto modal-dialog-centered dashboardModal justify-content-center">
<div class="card rounded-3 shadow">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h4 class="mb-0">QR Code</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body">
<canvas id="qrcode" class="rounded-3 shadow" ref="qrcode"></canvas>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,48 @@
<script>
export default {
name: "scheduleDropdown",
props: {
options: Array,
data: String,
edit: false
},
setup(props) {
if (props.data === undefined){
this.$emit('update', this.options[0].value)
}
},
computed:{
currentSelection(){
return this.options.find(x => x.value === this.data)
}
}
}
</script>
<template>
<div class="dropdown scheduleDropdown">
<button class="btn btn-sm btn-outline-primary rounded-3"
:class="{'disabled border-transparent': !edit}" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<samp>{{this.currentSelection.display}}</samp>
</button>
<ul class="dropdown-menu rounded-3 shadow" style="font-size: 0.875rem; width: 200px">
<li v-for="x in this.options" v-if="edit">
<a class="dropdown-item d-flex align-items-center" role="button" @click="$emit('update', x.value)">
<samp>{{x.display}}</samp>
<i class="bi bi-check ms-auto" v-if="x.value === this.currentSelection.value"></i>
</a>
</li>
</ul>
</div>
</template>
<style scoped>
.btn.disabled{
opacity: 1;
background-color: rgba(13, 110, 253, 0.09);
border-color: transparent;
}
</style>

View File

@@ -0,0 +1,220 @@
<script>
import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJobsComponents/scheduleDropdown.vue";
import {ref} from "vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchPost} from "@/utilities/fetch.js";
import VueDatePicker from "@vuepic/vue-datepicker";
import dayjs from "dayjs";
export default {
name: "schedulePeerJob",
components: {VueDatePicker, ScheduleDropdown},
props: {
dropdowns: Array[Object],
pjob: Object,
viewOnly: false
},
setup(props){
const job = ref({})
const edit = ref(false)
const newJob = ref(false)
job.value = JSON.parse(JSON.stringify(props.pjob))
if (!job.value.CreationDate){
edit.value = true
newJob.value = true
}
const store = DashboardConfigurationStore()
return {job, edit, newJob, store}
},
data(){
return {
inputType: undefined,
}
},
watch:{
pjob: {
deep: true,
immediate: true,
handler(newValue){
if (!this.edit){
this.job = JSON.parse(JSON.stringify(newValue))
}
}
}
},
methods: {
save(){
if (this.job.Field && this.job.Operator && this.job.Action && this.job.Value){
fetchPost(`/api/savePeerScheduleJob/`, {
Job: this.job
}, (res) => {
if (res.status){
this.edit = false;
this.store.newMessage("Server", "Job Saved!", "success")
console.log(res.data)
this.$emit("refresh", res.data[0])
this.newJob = false;
}else{
this.store.newMessage("Server", res.message, "danger")
}
})
}else{
this.alert();
}
},
alert(){
let animation = "animate__flash";
let dropdowns = this.$el.querySelectorAll(".scheduleDropdown");
let inputs = this.$el.querySelectorAll("input");
dropdowns.forEach(x => x.classList.add("animate__animated", animation))
inputs.forEach(x => x.classList.add("animate__animated", animation))
setTimeout(() => {
dropdowns.forEach(x => x.classList.remove("animate__animated", animation))
inputs.forEach(x => x.classList.remove("animate__animated", animation))
}, 2000)
},
reset(){
if(this.job.CreationDate){
this.job = JSON.parse(JSON.stringify(this.pjob));
this.edit = false;
}else{
this.$emit('delete')
}
},
delete(){
if(this.job.CreationDate){
fetchPost(`/api/deletePeerScheduleJob/`, {
Job: this.job
}, (res) => {
if (!res.status){
this.store.newMessage("Server", res.message, "danger")
this.$emit('delete')
}else{
this.store.newMessage("Server", "Job Deleted!", "success")
}
})
}
this.$emit('delete')
},
parseTime(modelData){
if(modelData){
this.job.Value = dayjs(modelData).format("YYYY-MM-DD HH:mm:ss");
}
}
},
}
</script>
<template>
<div class="card shadow-sm rounded-3 mb-2" :class="{'border-warning-subtle': this.newJob}">
<div class="card-header bg-transparent text-muted border-0">
<small class="d-flex" v-if="!this.newJob">
<strong class="me-auto">Job ID</strong>
<samp>{{this.job.JobID}}</samp>
</small>
<small v-else><span class="badge text-bg-warning">Unsaved Job</span></small>
</div>
<div class="card-body pt-1" style="font-family: var(--bs-font-monospace)">
<div class="d-flex gap-2 align-items-center mb-2">
<samp>
if
</samp>
<ScheduleDropdown
:edit="edit"
:options="this.dropdowns.Field"
:data="this.job.Field"
@update="(value) => {this.job.Field = value}"
></ScheduleDropdown>
<samp>
is
</samp>
<ScheduleDropdown
:edit="edit"
:options="this.dropdowns.Operator"
:data="this.job.Operator"
@update="(value) => this.job.Operator = value"
></ScheduleDropdown>
<VueDatePicker
:is24="true"
:min-date="new Date()"
:model-value="this.job.Value"
@update:model-value="this.parseTime" time-picker-inline
format="yyyy-MM-dd HH:mm:ss"
preview-format="yyyy-MM-dd HH:mm:ss"
:clearable="false"
:disabled="!edit"
v-if="this.job.Field === 'date'"
:dark="this.store.Configuration.Server.dashboard_theme === 'dark'"
/>
<!-- <input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1"-->
<!-- :disabled="!edit"-->
<!-- type="datetime-local"-->
<!-- v-if="this.job.Field === 'date'"-->
<!-- v-model="this.job.Value"-->
<!-- style="width: auto">-->
<input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1"
:disabled="!edit"
v-else
v-model="this.job.Value"
style="width: auto">
<samp>
{{this.dropdowns.Field.find(x => x.value === this.job.Field)?.unit}} {
</samp>
</div>
<div class="px-5 d-flex gap-2 align-items-center">
<samp>then</samp>
<ScheduleDropdown
:edit="edit"
:options="this.dropdowns.Action"
:data="this.job.Action"
@update="(value) => this.job.Action = value"
></ScheduleDropdown>
</div>
<div class="d-flex gap-3">
<samp>}</samp>
<div class="ms-auto d-flex gap-3" v-if="!this.edit">
<a role="button"
class="ms-auto text-decoration-none"
@click="this.edit = true">[E] Edit</a>
<a role="button"
@click="this.delete()"
class=" text-danger text-decoration-none">[D] Delete</a>
</div>
<div class="ms-auto d-flex gap-3" v-else>
<a role="button"
class="text-secondary text-decoration-none"
@click="this.reset()">[C] Cancel</a>
<a role="button"
class="text-primary ms-auto text-decoration-none"
@click="this.save()">[S] Save</a>
</div>
</div>
</div>
</div>
</template>
<style scoped>
*{
font-size: 0.875rem;
}
input{
padding: 0.1rem 0.4rem;
}
input:disabled{
border-color: transparent;
background-color: rgba(13, 110, 253, 0.09);
color: #0d6efd;
}
.dp__main{
width: auto;
flex-grow: 1;
--dp-input-padding: 2.5px 30px 2.5px 12px;
--dp-border-radius: 0.5rem;
}
</style>

View File

@@ -0,0 +1,228 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
export default {
name: "peerSearch",
setup(){
const store = DashboardConfigurationStore();
const wireguardConfigurationStore = WireguardConfigurationsStore()
return {store, wireguardConfigurationStore}
},
props: {
configuration: Object
},
data(){
return {
sort: {
status: "Status",
name: "Name",
allowed_ip: "Allowed IP",
restricted: "Restricted"
},
interval: {
'5000': '5 Seconds',
'10000': '10 Seconds',
'30000': '30 Seconds',
'60000': '1 Minutes'
},
searchString: "",
searchStringTimeout: undefined,
showDisplaySettings: false,
showMoreSettings: false
}
},
methods: {
debounce(){
if (!this.searchStringTimeout){
this.searchStringTimeout = setTimeout(() => {
this.wireguardConfigurationStore.searchString = this.searchString;
}, 300)
}else{
clearTimeout(this.searchStringTimeout)
this.searchStringTimeout = setTimeout(() => {
this.wireguardConfigurationStore.searchString = this.searchString;
}, 300)
}
},
updateSort(sort){
fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server",
key: "dashboard_sort",
value: sort
}, (res) => {
if (res.status){
this.store.getConfiguration();
}
})
},
updateRefreshInterval(refreshInterval){
fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server",
key: "dashboard_refresh_interval",
value: refreshInterval
}, (res) => {
if (res.status){
this.store.getConfiguration();
}
})
},
downloadAllPeer(){
fetchGet(`/api/downloadAllPeers/${this.configuration.Name}`, {}, (res) => {
console.log(res);
window.wireguard.generateZipFiles(res, this.configuration.Name)
})
}
},
mounted() {
}
}
</script>
<template>
<div class="mb-3">
<div class="d-flex gap-2 z-3 peerSearchContainer">
<RouterLink
to="create"
class="text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm">
<i class="bi bi-plus-lg me-2"></i>Peer
</RouterLink>
<button class="btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm"
@click="this.downloadAllPeer()">
<i class="bi bi-download me-2"></i> Download All
</button>
<div class="flex-grow-1 mt-3 mt-md-0">
<input class="form-control rounded-3 bg-secondary-subtle border-1 border-secondary-subtle shadow-sm w-100"
placeholder="Search..."
id="searchPeers"
@keyup="this.debounce()"
v-model="this.searchString">
</div>
<button
@click="this.showDisplaySettings = true"
class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
type="button" aria-expanded="false">
<i class="bi bi-filter-circle me-2"></i>
Display
</button>
<button class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
@click="this.showMoreSettings = true"
type="button" aria-expanded="false">
<i class="bi bi-three-dots"></i>
</button>
<Transition name="zoom">
<div
v-if="this.showDisplaySettings"
class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll displayModal">
<div class="container-md d-flex h-100 w-100">
<div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow w-100">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0 fw-normal">Display
</h4>
<button type="button" class="btn-close ms-auto" @click="this.showDisplaySettings = false"></button>
</div>
<div class="card-body px-4 pb-4 d-flex gap-3 flex-column">
<div>
<p class="text-muted fw-bold mb-2"><small>Sort by</small></p>
<div class="list-group">
<a v-for="(value, key) in this.sort" class="list-group-item list-group-item-action d-flex" role="button" @click="this.updateSort(key)">
<span class="me-auto">{{value}}</span>
<i class="bi bi-check text-primary"
v-if="store.Configuration.Server.dashboard_sort === key"></i>
</a>
</div>
</div>
<div>
<p class="text-muted fw-bold mb-2"><small>Refresh interval</small></p>
<div class="list-group">
<a v-for="(value, key) in this.interval"
class="list-group-item list-group-item-action d-flex" role="button"
@click="this.updateRefreshInterval(key)">
<span class="me-auto">{{value}}</span>
<i class="bi bi-check text-primary"
v-if="store.Configuration.Server.dashboard_refresh_interval === key"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Transition>
<Transition name="zoom">
<div
v-if="this.showMoreSettings"
class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll displayModal">
<div class="container-md d-flex h-100 w-100">
<div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow w-100">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0 fw-normal">Configuration Settings
</h4>
<button type="button" class="btn-close ms-auto" @click="this.showMoreSettings = false"></button>
</div>
<div class="card-body px-4 pb-4 d-flex gap-3 flex-column">
<div>
<p class="text-muted fw-bold mb-2"><small>Peer Jobs</small></p>
<div class="list-group">
<a class="list-group-item list-group-item-action d-flex" role="button"
@click="this.$emit('jobsAll')">
Active Jobs
</a>
<a class="list-group-item list-group-item-action d-flex" role="button"
@click="this.$emit('jobLogs')">
Logs
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Transition>
</div>
</div>
</template>
<style scoped>
.animation__fadeInDropdown{
animation-name: fadeInDropdown;
animation-duration: 0.2s;
animation-timing-function: cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
@keyframes fadeInDropdown{
0%{
opacity: 0;
filter: blur(3px);
transform: translateY(-60px);
}
100%{
opacity: 1;
filter: blur(0px);
transform: translateY(-40px);
}
}
.displayModal .dashboardModal{
width: 400px !important;
}
@media screen and (max-width: 768px) {
.peerSearchContainer{
flex-direction: column;
}
.peerSettingContainer .dashboardModal{
width: 100% !important;
}
}
</style>

View File

@@ -0,0 +1,233 @@
<script>
import {fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "peerSettings",
props: {
selectedPeer: Object
},
data(){
return {
data: undefined,
dataChanged: false,
showKey: false,
saving: false
}
},
setup(){
const dashboardConfigurationStore = DashboardConfigurationStore();
return {dashboardConfigurationStore}
},
methods: {
reset(){
if (this.selectedPeer){
this.data = JSON.parse(JSON.stringify(this.selectedPeer))
this.dataChanged = false;
}
},
savePeer(){
this.saving = true;
fetchPost(`/api/updatePeerSettings/${this.$route.params.id}`, this.data, (res) => {
this.saving = false;
if (res.status){
this.dashboardConfigurationStore.newMessage("Server", "Peer Updated!", "success")
}else{
this.dashboardConfigurationStore.newMessage("Server", res.message, "danger")
}
this.$emit("refresh")
})
},
resetPeerData(type){
this.saving = true
fetchPost(`/api/resetPeerData/${this.$route.params.id}`, {
id: this.data.id,
type: type
}, (res) => {
this.saving = false;
if (res.status){
this.dashboardConfigurationStore.newMessage("Server", "Peer data usage reset successfully.", "success")
}else{
this.dashboardConfigurationStore.newMessage("Server", res.message, "danger")
}
this.$emit("refresh")
})
}
},
beforeMount() {
this.reset();
},
mounted() {
this.$el.querySelectorAll("input").forEach(x => {
x.addEventListener("keyup", () => {
this.dataChanged = true;
});
})
}
}
</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">
<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 pb-2">
<h4 class="mb-0">Peer Settings</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4" v-if="this.data">
<div class="d-flex flex-column gap-2 mb-4">
<div class="d-flex align-items-center">
<small class="text-muted">Public Key</small>
<small class="ms-auto"><samp>{{this.data.id}}</samp></small>
</div>
<div>
<label for="peer_name_textbox" class="form-label">
<small class="text-muted">Name</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.name"
id="peer_name_textbox" placeholder="">
</div>
<div>
<div class="d-flex position-relative">
<label for="peer_private_key_textbox" class="form-label">
<small class="text-muted">Private Key <code>(Required for QR Code and Download)</code></small>
</label>
<a role="button" class="ms-auto text-decoration-none toggleShowKey"
@click="this.showKey = !this.showKey"
>
<i class="bi" :class="[this.showKey ? 'bi-eye-slash-fill':'bi-eye-fill']"></i>
</a>
</div>
<input :type="[this.showKey ? 'text':'password']" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.private_key"
id="peer_private_key_textbox"
style="padding-right: 40px">
</div>
<div>
<label for="peer_allowed_ip_textbox" class="form-label">
<small class="text-muted">Allowed IPs <code>(Required)</code></small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.allowed_ip"
id="peer_allowed_ip_textbox">
</div>
<div>
<label for="peer_endpoint_allowed_ips" class="form-label">
<small class="text-muted">Endpoint Allowed IPs <code>(Required)</code></small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.endpoint_allowed_ip"
id="peer_endpoint_allowed_ips">
</div>
<div>
<label for="peer_DNS_textbox" class="form-label">
<small class="text-muted">DNS</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.DNS"
id="peer_DNS_textbox">
</div>
<div class="accordion mt-3" id="peerSettingsAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button rounded-3 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#peerSettingsAccordionOptional">
Optional Settings
</button>
</h2>
<div id="peerSettingsAccordionOptional" class="accordion-collapse collapse"
data-bs-parent="#peerSettingsAccordion">
<div class="accordion-body d-flex flex-column gap-2 mb-2">
<div>
<label for="peer_preshared_key_textbox" class="form-label">
<small class="text-muted">Pre-Shared Key</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.preshared_key"
id="peer_preshared_key_textbox">
</div>
<div>
<label for="peer_mtu" class="form-label"><small class="text-muted">MTU</small></label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.mtu"
id="peer_mtu">
</div>
<div>
<label for="peer_keep_alive" class="form-label">
<small class="text-muted">Persistent Keepalive</small>
</label>
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.keepalive"
id="peer_keep_alive">
</div>
</div>
</div>
</div>
</div>
<hr>
<div class="d-flex gap-2 align-items-center">
<strong>Reset Data Usage</strong>
<div class="d-flex gap-2 ms-auto">
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
@click="this.resetPeerData('total')"
>
<i class="bi bi-arrow-down-up me-2"></i>
Total
</button>
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
@click="this.resetPeerData('receive')"
>
<i class="bi bi-arrow-down me-2"></i>
Received
</button>
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
@click="this.resetPeerData('sent')"
>
<i class="bi bi-arrow-up me-2"></i>
Sent
</button>
</div>
</div>
</div>
<div class="d-flex align-items-center gap-2">
<button class="btn btn-secondary rounded-3 shadow"
@click="this.reset()"
:disabled="!this.dataChanged || this.saving">
Revert <i class="bi bi-arrow-clockwise ms-2"></i>
</button>
<button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow"
:disabled="!this.dataChanged || this.saving"
@click="this.savePeer()"
>
Save Peer<i class="bi bi-save-fill ms-2"></i></button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.toggleShowKey{
position: absolute;
top: 35px;
right: 12px;
}
</style>

View File

@@ -0,0 +1,170 @@
<script>
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "peerSettingsDropdown",
setup(){
const dashboardStore = DashboardConfigurationStore()
return {dashboardStore}
},
props: {
Peer: Object
},
data(){
return{
deleteBtnDisabled: false,
restrictBtnDisabled: false,
allowAccessBtnDisabled: false,
}
},
methods: {
downloadPeer(){
fetchGet("/api/downloadPeer/"+this.$route.params.id, {
id: this.Peer.id
}, (res) => {
if (res.status){
const blob = new Blob([res.data.file], { type: "text/plain" });
const jsonObjectUrl = URL.createObjectURL(blob);
const filename = `${res.data.fileName}.conf`;
const anchorEl = document.createElement("a");
anchorEl.href = jsonObjectUrl;
anchorEl.download = filename;
anchorEl.click();
this.dashboardStore.newMessage("WGDashboard", "Peer download started", "success")
}else{
this.dashboardStore.newMessage("Server", res.message, "danger")
}
})
},
downloadQRCode(){
fetchGet("/api/downloadPeer/"+this.$route.params.id, {
id: this.Peer.id
}, (res) => {
if (res.status){
this.$emit("qrcode", res.data.file)
}else{
this.dashboardStore.newMessage("Server", res.message, "danger")
}
})
},
deletePeer(){
this.deleteBtnDisabled = true
fetchPost(`/api/deletePeers/${this.$route.params.id}`, {
peers: [this.Peer.id]
}, (res) => {
this.dashboardStore.newMessage("Server", res.message, res.status ? "success":"danger")
this.$emit("refresh")
this.deleteBtnDisabled = false
})
},
restrictPeer(){
this.restrictBtnDisabled = true
fetchPost(`/api/restrictPeers/${this.$route.params.id}`, {
peers: [this.Peer.id]
}, (res) => {
this.dashboardStore.newMessage("Server", res.message, res.status ? "success":"danger")
this.$emit("refresh")
this.restrictBtnDisabled = false
})
},
allowAccessPeer(){
this.allowAccessBtnDisabled = true
fetchPost(`/api/allowAccessPeers/${this.$route.params.id}`, {
peers: [this.Peer.id]
}, (res) => {
this.dashboardStore.newMessage("Server", res.message, res.status ? "success":"danger")
this.$emit("refresh")
this.allowAccessBtnDisabled = false
})
}
}
}
</script>
<template>
<ul class="dropdown-menu mt-2 shadow-lg d-block rounded-3" style="max-width: 200px">
<template v-if="!this.Peer.restricted">
<template v-if="!this.Peer.private_key">
<li>
<small class="w-100 dropdown-item text-muted"
style="white-space: break-spaces; font-size: 0.7rem"
>Download & QR Code is not available due to no <code>private key</code>
set for this peer
</small>
</li>
</template>
<template v-else>
<li class="d-flex" style="padding-left: var(--bs-dropdown-item-padding-x); padding-right: var(--bs-dropdown-item-padding-x);">
<a class="dropdown-item text-center px-0 rounded-3" role="button" @click="this.downloadPeer()">
<i class="me-auto bi bi-download"></i>
</a>
<a class="dropdown-item text-center px-0 rounded-3" role="button"
@click="this.downloadQRCode()">
<i class="me-auto bi bi-qr-code"></i>
</a>
<a class="dropdown-item text-center px-0 rounded-3" role="button" @click="this.$emit('share')">
<i class="me-auto bi bi-share"></i>
</a>
</li>
</template>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item d-flex" role="button"
@click="this.$emit('setting')"
>
<i class="me-auto bi bi-pen"></i> Edit
</a>
</li>
<li>
<a class="dropdown-item d-flex" role="button"
@click="this.$emit('jobs')"
>
<i class="me-auto bi bi-app-indicator"></i> Schedule Jobs
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item d-flex text-warning"
@click="this.restrictPeer()"
:class="{disabled: this.restrictBtnDisabled}"
role="button">
<i class="me-auto bi bi-lock"></i> {{!this.restrictBtnDisabled ? "Restrict Access":"Restricting..."}}
</a>
</li>
<li>
<a class="dropdown-item d-flex fw-bold text-danger"
@click="this.deletePeer()"
:class="{disabled: this.deleteBtnDisabled}"
role="button">
<i class="me-auto bi bi-trash"></i> {{!this.deleteBtnDisabled ? "Delete":"Deleting..."}}
</a>
</li>
</template>
<template v-else>
<li>
<a class="dropdown-item d-flex text-warning"
@click="this.allowAccessPeer()"
:class="{disabled: this.restrictBtnDisabled}"
role="button">
<i class="me-auto bi bi-unlock"></i>
{{!this.allowAccessBtnDisabled ? "Allow Access":"Allowing..."}}
</a>
</li>
</template>
</ul>
</template>
<style scoped>
.dropdown-menu{
right: 1rem;
min-width: 200px;
}
.dropdown-item.disabled, .dropdown-item:disabled{
opacity: 0.7;
}
</style>

View File

@@ -0,0 +1,172 @@
<script>
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {fetchPost} from "@/utilities/fetch.js";
import dayjs from "dayjs";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import VueDatePicker from '@vuepic/vue-datepicker';
export default {
name: "peerShareLinkModal",
props: {
peer: Object
},
components: {
VueDatePicker
},
data(){
return {
dataCopy: undefined,
loading: false
}
},
setup(){
const store = DashboardConfigurationStore();
return {store}
},
mounted() {
this.dataCopy = JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0);
},
watch: {
'peer.ShareLink': {
deep: true,
handler(newVal, oldVal){
if (oldVal.length !== newVal.length){
this.dataCopy = JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0);
}
}
}
},
methods: {
startSharing(){
this.loading = true;
fetchPost("/api/sharePeer/create", {
Configuration: this.peer.configuration.Name,
Peer: this.peer.id,
ExpireDate: dayjs().add(7, 'd').format("YYYY-MM-DD HH:mm:ss")
}, (res) => {
if (res.status){
this.peer.ShareLink = res.data;
this.dataCopy = res.data.at(0);
this.store.newMessage("Server", "Share link created successfully", "success")
}else{
this.store.newMessage("Server",
"Share link failed to create. Reason: " + res.message, "danger")
}
this.loading = false;
})
},
updateLinkExpireDate(){
fetchPost("/api/sharePeer/update", this.dataCopy, (res) => {
if (res.status){
this.dataCopy = res.data.at(0)
this.peer.ShareLink = res.data;
this.store.newMessage("Server", "Link expire date updated", "success")
}else{
this.store.newMessage("Server",
"Link expire date failed to update. Reason: " + res.message, "danger")
}
this.loading = false
});
},
stopSharing(){
this.loading = true;
this.dataCopy.ExpireDate = dayjs().format("YYYY-MM-DD HH:mm:ss")
this.updateLinkExpireDate()
},
parseTime(modelData){
if(modelData){
this.dataCopy.ExpireDate = dayjs(modelData).format("YYYY-MM-DD HH:mm:ss");
}else{
this.dataCopy.ExpireDate = undefined
}
this.updateLinkExpireDate()
}
},
computed: {
getUrl(){
const crossServer = this.store.getActiveCrossServer();
if(crossServer){
return `${crossServer.host}/${this.$router.resolve(
{path: "/share", query: {"ShareID": this.dataCopy.ShareID}}).href}`
}
return window.location.origin
+ window.location.pathname
+ this.$router.resolve(
{path: "/share", query: {"ShareID": this.dataCopy.ShareID}}).href;
}
}
}
</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: 500px">
<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">Share Peer</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body px-4 pb-4" v-if="this.peer.ShareLink">
<div v-if="!this.dataCopy">
<h6 class="mb-3 text-muted">
Currently the peer is not sharing
</h6>
<button
@click="this.startSharing()"
:disabled="this.loading"
class="w-100 btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm">
<span :class="{'animate__animated animate__flash animate__infinite animate__slower': this.loading}">
<i class="bi bi-send-fill me-2" ></i>
</span>
{{this.loading ? "Sharing...":"Start Sharing"}}
</button>
</div>
<div v-else>
<div class="d-flex gap-2 mb-4">
<i class="bi bi-link-45deg"></i>
<a :href="this.getUrl"
class="text-decoration-none" target="_blank">
{{ getUrl }}
</a>
</div>
<div class="d-flex flex-column gap-2 mb-3">
<small>
<i class="bi bi-calendar me-2"></i>
Expire Date
</small>
<VueDatePicker
:is24="true"
:min-date="new Date()"
:model-value="this.dataCopy.ExpireDate"
@update:model-value="this.parseTime" time-picker-inline
format="yyyy-MM-dd HH:mm:ss"
preview-format="yyyy-MM-dd HH:mm:ss"
:dark="this.store.Configuration.Server.dashboard_theme === 'dark'"
/>
</div>
<button
@click="this.stopSharing()"
:disabled="this.loading"
class="w-100 btn bg-danger-subtle text-danger-emphasis border-1 border-danger-subtle rounded-3 shadow-sm">
<span :class="{'animate__animated animate__flash animate__infinite animate__slower': this.loading}">
<i class="bi bi-send-slash-fill me-2" ></i>
</span>
{{this.loading ? "Stop Sharing...":"Stop Sharing"}}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,23 @@
<script>
export default {
name: "restrictedPeers"
}
</script>
<template>
<div class="container">
<div class="mb-4">
<RouterLink to="peers" is="div" class="d-flex align-items-center gap-4 text-decoration-none">
<h3 class="mb-0 text-body">
<i class="bi bi-chevron-left"></i>
</h3>
<h3 class="text-body mb-0">Restricted Peers</h3>
</RouterLink>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,76 @@
<script>
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import ConfigurationCard from "@/components/configurationListComponents/configurationCard.vue";
export default {
name: "configurationList",
components: {ConfigurationCard},
async setup(){
const wireguardConfigurationsStore = WireguardConfigurationsStore();
return {wireguardConfigurationsStore}
},
data(){
return {
configurationLoaded: false
}
},
async mounted() {
await this.wireguardConfigurationsStore.getConfigurations();
this.configurationLoaded = true;
this.wireguardConfigurationsStore.ConfigurationListInterval = setInterval(() => {
this.wireguardConfigurationsStore.getConfigurations()
}, 10000)
},
beforeUnmount() {
clearInterval(this.wireguardConfigurationsStore.ConfigurationListInterval)
}
}
</script>
<template>
<div class="mt-md-5 mt-3">
<div class="container-md">
<div class="d-flex mb-4 configurationListTitle">
<h3 class="text-body d-flex">
<i class="bi bi-body-text me-2"></i>
<span>WireGuard Configurations</span></h3>
<RouterLink to="/new_configuration" class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto rounded-3">
<i class="bi bi-plus-circle-fill me-2"></i>
Configuration
</RouterLink>
</div>
<Transition name="fade" mode="out-in">
<div v-if="this.configurationLoaded">
<p class="text-muted" v-if="this.wireguardConfigurationsStore.Configurations.length === 0">
You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".
</p>
<div class="d-flex gap-3 flex-column mb-3" v-else>
<ConfigurationCard v-for="c in this.wireguardConfigurationsStore.Configurations" :key="c.Name" :c="c"></ConfigurationCard>
</div>
</div>
</Transition>
</div>
</div>
</template>
<style scoped>
@media screen and (max-width: 768px) {
.configurationListTitle{
flex-direction: column;
gap: 0.5rem;
h3 span{
margin-left: auto !important;
}
.btn{
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,98 @@
<script>
import {fetchGet} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "configurationCard",
props: {
c: {
Name: String,
Status: Boolean,
PublicKey: String,
PrivateKey: String
}
},
data(){
return{
configurationToggling: false
}
},
setup(){
const dashboardConfigurationStore = DashboardConfigurationStore();
return {dashboardConfigurationStore}
},
methods: {
toggle(){
this.configurationToggling = true;
fetchGet("/api/toggleWireguardConfiguration/", {
configurationName: this.c.Name
}, (res) => {
if (res.status){
this.dashboardConfigurationStore.newMessage("Server",
`${this.c.Name} is ${res.data ? 'is on':'is off'}`)
}else{
this.dashboardConfigurationStore.newMessage("Server",
res.message, 'danger')
}
this.c.Status = res.data
this.configurationToggling = false;
})
}
}
}
</script>
<template>
<div class="card conf_card rounded-3 shadow text-decoration-none">
<RouterLink :to="'/configuration/' + c.Name + '/peers'" class="card-body d-flex align-items-center gap-3 flex-wrap text-decoration-none">
<h6 class="mb-0"><span class="dot" :class="{active: c.Status}"></span></h6>
<h6 class="card-title mb-0"><samp>{{c.Name}}</samp></h6>
<h6 class="mb-0 ms-auto">
<i class="bi bi-chevron-right"></i>
</h6>
</RouterLink>
<div class="card-footer d-flex gap-2 flex-column">
<div class="row">
<small class="col-6 col-md-3">
<i class="bi bi-arrow-down-up me-2"></i>{{c.DataUsage.Total > 0 ? c.DataUsage.Total.toFixed(4) : 0}} GB
</small>
<small class="text-primary-emphasis col-6 col-md-3">
<i class="bi bi-arrow-down me-2"></i>{{c.DataUsage.Receive > 0 ? c.DataUsage.Receive.toFixed(4) : 0}} GB
</small>
<small class="text-success-emphasis col-6 col-md-3">
<i class="bi bi-arrow-up me-2"></i>{{c.DataUsage.Sent > 0 ? c.DataUsage.Sent.toFixed(4) : 0}} GB
</small>
<small class="text-md-end col-6 col-md-3">
<span class="dot me-2" :class="{active: c.ConnectedPeers > 0}"></span>{{c.ConnectedPeers}} Peers
</small>
</div>
<div class="d-flex align-items-center gap-2">
<small class="text-muted">
<strong style="word-break: keep-all">Public Key</strong>
</small>
<small class="mb-0 d-block d-lg-inline-block ">
<samp style="line-break: anywhere">{{c.PublicKey}}</samp>
</small>
<div class="form-check form-switch ms-auto">
<label class="form-check-label" style="cursor: pointer" :for="'switch' + c.PrivateKey">
{{this.configurationToggling ? 'Turning ':''}}
{{c.Status ? "On":"Off"}}
<span v-if="this.configurationToggling"
class="spinner-border spinner-border-sm" aria-hidden="true"></span>
</label>
<input class="form-check-input"
style="cursor: pointer"
:disabled="this.configurationToggling"
type="checkbox" role="switch" :id="'switch' + c.PrivateKey"
@change="this.toggle()"
v-model="c.Status"
>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,32 @@
<script>
export default {
name: "message",
props: {
message: Object
},
mounted() {
setTimeout(() => {
this.message.show = false
}, 5000)
}
}
</script>
<template>
<div class="card shadow rounded-3 position-relative mb-2"
:class="{
'text-bg-danger': this.message.type === 'danger',
'text-bg-success': this.message.type === 'success',
'text-bg-warning': this.message.type === 'warning'}"
:id="this.message.id"
style="width: 400px">
<div class="card-body">
<small class="fw-bold d-block" style="text-transform: uppercase">FROM {{this.message.from}}</small>
{{this.message.content}}
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,140 @@
<script>
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchGet} from "@/utilities/fetch.js";
export default {
name: "navbar",
setup(){
const wireguardConfigurationsStore = WireguardConfigurationsStore();
const dashboardConfigurationStore = DashboardConfigurationStore();
return {wireguardConfigurationsStore, dashboardConfigurationStore}
},
data(){
return {
updateAvailable: false,
updateMessage: "Checking for update...",
updateUrl: ""
}
},
mounted() {
fetchGet("/api/getDashboardUpdate", {}, (res) => {
if (res.status){
if (res.data){
this.updateAvailable = true
this.updateUrl = res.data
}
this.updateMessage = res.message
}else{
this.updateMessage = "Failed to check available update"
console.log(`Failed to get update: ${res.message}`)
}
})
}
}
</script>
<template>
<div class="col-md-3 col-lg-2 d-md-block p-3 navbar-container"
:class="{active: this.dashboardConfigurationStore.ShowNavBar}"
:data-bs-theme="dashboardConfigurationStore.Configuration.Server.dashboard_theme"
style="height: calc(-50px + 100vh);">
<nav id="sidebarMenu" class=" bg-body-tertiary sidebar border h-100 rounded-3 shadow overflow-y-scroll" >
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column px-2">
<li class="nav-item">
<RouterLink class="nav-link rounded-3"
to="/" exact-active-class="active">
<i class="bi bi-house me-2"></i>
Home</RouterLink></li>
<li class="nav-item">
<RouterLink class="nav-link rounded-3" to="/settings"
exact-active-class="active">
<i class="bi bi-gear me-2"></i>
Settings</RouterLink></li>
</ul>
<hr class="text-body">
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted text-center">
<i class="bi bi-body-text me-2"></i>
Configurations
</h6>
<ul class="nav flex-column px-2">
<li class="nav-item">
<RouterLink :to="'/configuration/'+c.Name + '/peers'" class="nav-link nav-conf-link rounded-3"
active-class="active"
v-for="c in this.wireguardConfigurationsStore.Configurations">
<span class="dot me-2" :class="{active: c.Status}"></span>
{{c.Name}}
</RouterLink>
</li>
</ul>
<hr class="text-body">
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted text-center">
<i class="bi bi-tools me-2"></i>
Tools
</h6>
<ul class="nav flex-column px-2">
<li class="nav-item">
<RouterLink to="/ping" class="nav-link rounded-3" active-class="active">Ping</RouterLink></li>
<li class="nav-item">
<RouterLink to="/traceroute" class="nav-link rounded-3" active-class="active">Traceroute</RouterLink>
</li>
</ul>
<hr class="text-body">
<ul class="nav flex-column px-2">
<li class="nav-item"><a class="nav-link text-danger rounded-3"
@click="this.dashboardConfigurationStore.signOut()"
role="button" style="font-weight: bold">
<i class="bi bi-box-arrow-left me-2"></i>
Sign Out</a>
</li>
<li class="nav-item" style="font-size: 0.8rem">
<a :href="this.updateUrl" v-if="this.updateAvailable" class="text-decoration-none" target="_blank">
<small class="nav-link text-muted rounded-3" >
{{ this.updateMessage }}
</small>
</a>
<small class="nav-link text-muted" v-else>
{{ this.updateMessage }}
</small>
</li>
</ul>
</div>
</nav>
</div>
</template>
<style scoped>
@media screen and (max-width: 768px) {
.navbar-container{
position: absolute;
z-index: 1000;
animation-duration: 0.4s;
animation-fill-mode: both;
display: none;
animation-timing-function: cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
.navbar-container.active{
animation-direction: normal;
display: block !important;
animation-name: zoomInFade
}
}
@keyframes zoomInFade {
0%{
opacity: 0;
transform: translateY(60px);
filter: blur(3px);
}
100%{
opacity: 1;
transform: translateY(0px);
filter: blur(0px);
}
}
</style>

View File

@@ -0,0 +1,120 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "accountSettingsInputPassword",
props:{
targetData: String,
warning: false,
warningText: ""
},
setup(){
const store = DashboardConfigurationStore();
const uuid = `input_${v4()}`;
return {store, uuid};
},
data(){
return{
value: {
currentPassword: "",
newPassword: "",
repeatNewPassword: ""
},
invalidFeedback: "",
showInvalidFeedback: false,
isValid: false,
timeout: undefined
}
},
methods:{
async useValidation(){
if (Object.values(this.value).find(x => x.length === 0) === undefined){
if (this.value.newPassword === this.value.repeatNewPassword){
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Account",
key: this.targetData,
value: this.value
}, (res) => {
if (res.status){
this.isValid = true;
this.showInvalidFeedback = false;
this.store.Configuration.Account[this.targetData] = this.value
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.isValid = false;
this.value = {
currentPassword: "",
newPassword: "",
repeatNewPassword: ""
}
}, 5000);
}else{
this.isValid = false;
this.showInvalidFeedback = true;
this.invalidFeedback = res.message
}
})
}else{
this.showInvalidFeedback = true;
this.invalidFeedback = "New passwords does not match"
}
}else{
this.showInvalidFeedback = true;
this.invalidFeedback = "Please fill in all required fields."
}
}
}
}
</script>
<template>
<div class="d-flex flex-column">
<div class="row">
<div class="col-sm">
<div class="form-group mb-2">
<label :for="'currentPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>Current Password</small></strong>
</label>
<input type="password" class="form-control mb-2"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.currentPassword"
:id="'currentPassword_' + this.uuid">
<div class="invalid-feedback d-block" v-if="showInvalidFeedback">{{this.invalidFeedback}}</div>
</div>
</div>
<div class="col-sm">
<div class="form-group mb-2">
<label :for="'newPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>New Password</small></strong>
</label>
<input type="password" class="form-control mb-2"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.newPassword"
:id="'newPassword_' + this.uuid">
</div>
</div>
<div class="col-sm">
<div class="form-group mb-2">
<label :for="'repeatNewPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>Repeat New Password</small></strong>
</label>
<input type="password" class="form-control mb-2"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
v-model="this.value.repeatNewPassword"
:id="'repeatNewPassword_' + this.uuid">
</div>
</div>
</div>
<button class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm" @click="this.useValidation()">
<i class="bi bi-save2-fill me-2"></i>Update Password
</button>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,86 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "accountSettingsInputUsername",
props:{
targetData: String,
title: String,
warning: false,
warningText: ""
},
setup(){
const store = DashboardConfigurationStore();
const uuid = `input_${v4()}`;
return {store, uuid};
},
data(){
return{
value:"",
invalidFeedback: "",
showInvalidFeedback: false,
isValid: false,
timeout: undefined,
changed: false,
updating: false,
}
},
mounted() {
this.value = this.store.Configuration.Account[this.targetData];
},
methods:{
async useValidation(){
if (this.changed){
this.updating = true
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Account",
key: this.targetData,
value: this.value
}, (res) => {
if (res.status){
this.isValid = true;
this.showInvalidFeedback = false;
this.store.Configuration.Account[this.targetData] = this.value
clearTimeout(this.timeout)
this.timeout = setTimeout(() => this.isValid = false, 5000);
}else{
this.isValid = false;
this.showInvalidFeedback = true;
this.invalidFeedback = res.message
}
this.changed = false
this.updating = false;
})
}
}
}
}
</script>
<template>
<div class="form-group mb-2">
<label :for="this.uuid" class="text-muted mb-1">
<strong><small>{{this.title}}</small></strong>
</label>
<input type="text" class="form-control"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
:id="this.uuid"
v-model="this.value"
@keydown="this.changed = true"
@blur="useValidation()"
:disabled="this.updating"
>
<div class="invalid-feedback">{{this.invalidFeedback}}</div>
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1"
v-if="warning"
>
<small><i class="bi bi-exclamation-triangle-fill me-2"></i><span v-html="warningText"></span></small>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,63 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "accountSettingsMFA",
setup(){
const store = DashboardConfigurationStore();
const uuid = `input_${v4()}`;
return {store, uuid};
},
data(){
return {
status: false
}
},
mounted() {
this.status = this.store.Configuration.Account["enable_totp"]
},
methods: {
async resetMFA(){
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Account",
key: "totp_verified",
value: "false"
}, async (res) => {
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Account",
key: "enable_totp",
value: "false"
}, (res) => {
if (res.status){
this.$router.push("/2FASetup")
}
})
})
}
}
}
</script>
<template>
<div>
<div class="d-flex align-items-center">
<strong>Multi-Factor Authentication</strong>
<div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox"
v-model="this.status"
role="switch" id="allowMFAKeysSwitch">
</div>
<button class="btn bg-warning-subtle text-warning-emphasis border-1 border-warning-subtle ms-auto rounded-3 shadow-sm"
v-if="this.status" @click="this.resetMFA()">
<i class="bi bi-shield-lock-fill me-2"></i>
{{this.store.Configuration.Account["totp_verified"] ? "Reset" : "Setup" }} MFA
</button>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,130 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid";
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import NewDashboardAPIKey from "@/components/settingsComponent/dashboardAPIKeysComponents/newDashboardAPIKey.vue";
import DashboardAPIKey from "@/components/settingsComponent/dashboardAPIKeysComponents/dashboardAPIKey.vue";
export default {
name: "dashboardAPIKeys",
components: {DashboardAPIKey, NewDashboardAPIKey},
setup(){
const store = DashboardConfigurationStore();
return {store};
},
data(){
return {
value: this.store.Configuration.Server.dashboard_api_key,
apiKeys: [],
newDashboardAPIKey: false
}
},
methods: {
async toggleDashboardAPIKeys(){
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server",
key: "dashboard_api_key",
value: this.value
}, (res) => {
if (res.status){
this.store.Configuration.Peers[this.targetData] = this.value;
this.store.newMessage("Server",
`API Keys function is successfully ${this.value ? 'enabled':'disabled'}`, "success")
}else{
this.value = this.store.Configuration.Peers[this.targetData];
this.store.newMessage("Server",
`API Keys function is failed ${this.value ? 'enabled':'disabled'}`, "danger")
}
})
},
},
watch: {
value:{
immediate: true,
handler(newValue){
if (newValue){
fetchGet("/api/getDashboardAPIKeys", {}, (res) => {
console.log(res)
if(res.status){
this.apiKeys = res.data
}else{
this.apiKeys = []
this.store.newMessage("Server", res.message, "danger")
}
})
}else{
this.apiKeys = []
}
}
}
}
}
</script>
<template>
<div class="card mb-4 shadow rounded-3">
<div class="card-header d-flex">
API Keys
<div class="form-check form-switch ms-auto" v-if="!this.store.getActiveCrossServer()">
<input class="form-check-input" type="checkbox"
v-model="this.value"
@change="this.toggleDashboardAPIKeys()"
role="switch" id="allowAPIKeysSwitch">
<label class="form-check-label" for="allowAPIKeysSwitch">
{{this.value ? 'Enabled':'Disabled'}}
</label>
</div>
</div>
<div class="card-body position-relative d-flex flex-column gap-2" v-if="this.value">
<button class="ms-auto btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle rounded-3 shadow-sm"
@click="this.newDashboardAPIKey = true"
v-if="!this.store.getActiveCrossServer()"
>
<i class="bi bi-key me-2"></i> Create
</button>
<div class="card" style="height: 300px" v-if="this.apiKeys.length === 0">
<div class="card-body d-flex text-muted">
<span class="m-auto">
No Dashboard API Key
</span>
</div>
</div>
<div class="d-flex flex-column gap-2 position-relative" v-else style="min-height: 300px">
<TransitionGroup name="apiKey">
<DashboardAPIKey v-for="key in this.apiKeys" :apiKey="key"
:key="key.Key"
@deleted="(nkeys) => this.apiKeys = nkeys"></DashboardAPIKey>
</TransitionGroup>
</div>
<Transition name="zoomReversed">
<NewDashboardAPIKey v-if="this.newDashboardAPIKey"
@created="(data) => this.apiKeys = data"
@close="this.newDashboardAPIKey = false"
></NewDashboardAPIKey>
</Transition>
</div>
</div>
</template>
<style scoped>
.apiKey-move, /* apply transition to moving elements */
.apiKey-enter-active,
.apiKey-leave-active {
transition: all 0.5s ease;
}
.apiKey-enter-from,
.apiKey-leave-to {
opacity: 0;
transform: translateY(30px) scale(0.9);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.apiKey-leave-active {
position: absolute;
width: 100%;
}
</style>

View File

@@ -0,0 +1,89 @@
<script>
import {fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "dashboardAPIKey",
props: {
apiKey: Object
},
setup(){
const store = DashboardConfigurationStore();
return {store};
},
data(){
return {
confirmDelete: false
}
},
methods: {
deleteAPIKey(){
fetchPost("/api/deleteDashboardAPIKey", {
Key: this.apiKey.Key
}, (res) => {
if (res.status){
this.$emit('deleted', res.data);
this.store.newMessage("Server", "API Key deleted", "success");
}else{
this.store.newMessage("Server", res.message, "danger")
}
})
}
}
}
</script>
<template>
<div class="card rounded-3 shadow-sm">
<div class="card-body d-flex gap-3 align-items-center apiKey-card-body" v-if="!this.confirmDelete">
<div class="d-flex align-items-center gap-2">
<small class="text-muted">Key</small>
<span style="word-break: break-all">{{this.apiKey.Key}}</span>
</div>
<div class="d-flex align-items-center gap-2 ms-auto">
<small class="text-muted">Expire At</small>
{{this.apiKey.ExpiredAt ? this.apiKey.ExpiredAt : 'Never'}}
</div>
<a role="button" class="btn btn-sm bg-danger-subtle text-danger-emphasis rounded-3"
v-if="!this.store.getActiveCrossServer()"
@click="this.confirmDelete = true">
<i class="bi bi-trash-fill"></i>
</a>
</div>
<div v-else class="card-body d-flex gap-3 align-items-center justify-content-end"
v-if="!this.store.getActiveCrossServer()">
Are you sure to delete this API key?
<a role="button" class="btn btn-sm bg-success-subtle text-success-emphasis rounded-3"
@click="this.deleteAPIKey()"
>
<i class="bi bi-check-lg"></i>
</a>
<a role="button" class="btn btn-sm bg-secondary-subtle text-secondary-emphasis rounded-3"
@click="this.confirmDelete = false">
<i class="bi bi-x-lg"></i>
</a>
</div>
</div>
</template>
<style scoped>
@media screen and (max-width: 992px) {
.apiKey-card-body{
flex-direction: column !important;
align-items: start !important;
div.ms-auto{
margin-left: 0 !important;
}
div{
width: 100%;
align-items: start !important;
}
small{
margin-right: auto;
}
}
}
</style>

View File

@@ -0,0 +1,100 @@
<script>
import dayjs from "dayjs";
import {fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import VueDatePicker from "@vuepic/vue-datepicker";
export default {
name: "newDashboardAPIKey",
components: {VueDatePicker},
data(){
return{
newKeyData:{
ExpiredAt: dayjs().add(7, 'd').format("YYYY-MM-DD HH:mm:ss"),
neverExpire: false
},
submitting: false
}
},
setup(){
const store = DashboardConfigurationStore();
return {store};
},
mounted() {
console.log(this.newKeyData.ExpiredAt)
},
methods: {
submitNewAPIKey(){
this.submitting = true;
fetchPost('/api/newDashboardAPIKey', this.newKeyData, (res) => {
if (res.status){
this.$emit('created', res.data);
this.store.newMessage("Server", "New API Key created", "success");
this.$emit('close')
}else{
this.store.newMessage("Server", res.message, "danger")
}
this.submitting = false;
})
},
fixDate(date){
console.log(dayjs(date).format("YYYY-MM-DDTHH:mm:ss"))
return dayjs(date).format("YYYY-MM-DDTHH:mm:ss")
},
parseTime(modelData){
if(modelData){
this.newKeyData.ExpiredAt = dayjs(modelData).format("YYYY-MM-DD HH:mm:ss");
}else{
this.newKeyData.ExpiredAt = undefined
}
}
}
}
</script>
<template>
<div class="position-absolute w-100 h-100 top-0 start-0 rounded-bottom-3 p-3 d-flex"
style="background-color: #00000060; backdrop-filter: blur(3px)">
<div class="card m-auto rounded-3 mt-5">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h6 class="mb-0">Create API Key</h6>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div>
<div class="card-body d-flex gap-2 p-4 flex-column">
<small class="text-muted">When should this API Key expire?</small>
<div class="d-flex align-items-center gap-2">
<VueDatePicker
:is24="true"
:min-date="new Date()"
:model-value="this.newKeyData.ExpiredAt"
@update:model-value="this.parseTime" time-picker-inline
format="yyyy-MM-dd HH:mm:ss"
preview-format="yyyy-MM-dd HH:mm:ss"
:clearable="false"
:disabled="this.newKeyData.neverExpire || this.submitting"
:dark="this.store.Configuration.Server.dashboard_theme === 'dark'"
/>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox"
v-model="this.newKeyData.neverExpire" id="neverExpire" :disabled="this.submitting">
<label class="form-check-label" for="neverExpire">
Never Expire (<i class="bi bi-emoji-grimace-fill"></i> Don't think that's a good idea)
</label>
</div>
<button class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm"
:class="{disabled: this.submitting}"
@click="this.submitNewAPIKey()"
>
<i class="bi bi-check-lg me-2" v-if="!this.submitting"></i>
{{this.submitting ? 'Creating...':'Done'}}
</button>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,91 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "dashboardSettingsInputIPAddressAndPort",
props:{
// targetData: String,
// title: String,
// warning: false,
// warningText: ""
},
setup(){
const store = DashboardConfigurationStore();
const uuid = `input_${v4()}`;
return {store, uuid};
},
data(){
return{
app_ip:"",
app_port:"",
invalidFeedback: "",
showInvalidFeedback: false,
isValid: false,
timeout: undefined,
changed: false,
updating: false,
}
},
mounted() {
this.app_ip = this.store.Configuration.Server.app_ip;
this.app_port = this.store.Configuration.Server.app_port;
},
methods:{
async useValidation(){
if(this.changed){
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server",
key: this.targetData,
value: this.value
}, (res) => {
if (res.status){
this.isValid = true;
this.showInvalidFeedback = false;
this.store.Configuration.Account[this.targetData] = this.value
clearTimeout(this.timeout)
this.timeout = setTimeout(() => this.isValid = false, 5000);
}else{
this.isValid = false;
this.showInvalidFeedback = true;
this.invalidFeedback = res.message
}
})
}
}
}
}
</script>
<template>
<div>
<div class="invalid-feedback d-block mt-0">{{this.invalidFeedback}}</div>
<div class="row">
<div class="form-group mb-2 col-sm">
<label :for="'app_ip_' + this.uuid" class="text-muted mb-1">
<strong><small>Dashboard IP Address</small></strong>
</label>
<input type="text" class="form-control mb-2" :id="'app_ip_' + this.uuid" v-model="this.app_ip">
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block">
<small><i class="bi bi-exclamation-triangle-fill me-2"></i><code>0.0.0.0</code> means it can be access by anyone with your server
IP Address.</small>
</div>
</div>
<div class="form-group col-sm">
<label :for="'app_port_' + this.uuid" class="text-muted mb-1">
<strong><small>Dashboard Port</small></strong>
</label>
<input type="text" class="form-control mb-2" :id="'app_port_' + this.uuid" v-model="this.app_port">
</div>
</div>
<button class="btn btn-success btn-sm fw-bold rounded-3">
<i class="bi bi-floppy-fill me-2"></i>Update Dashboard Settings & Restart
</button>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,86 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "dashboardSettingsInputWireguardConfigurationPath",
props:{
targetData: String,
title: String,
warning: false,
warningText: ""
},
setup(){
const store = DashboardConfigurationStore();
const uuid = `input_${v4()}`;
return {store, uuid};
},
data(){
return{
value:"",
invalidFeedback: "",
showInvalidFeedback: false,
isValid: false,
timeout: undefined,
changed: false,
updating: false,
}
},
mounted() {
this.value = this.store.Configuration.Server[this.targetData];
},
methods:{
async useValidation(){
if(this.changed){
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server",
key: this.targetData,
value: this.value
}, (res) => {
if (res.status){
this.isValid = true;
this.showInvalidFeedback = false;
this.store.Configuration.Account[this.targetData] = this.value
clearTimeout(this.timeout)
this.timeout = setTimeout(() => this.isValid = false, 5000);
}else{
this.isValid = false;
this.showInvalidFeedback = true;
this.invalidFeedback = res.message
}
this.changed = false;
this.updating = false
})
}
}
}
}
</script>
<template>
<div class="form-group mb-2">
<label :for="this.uuid" class="text-muted mb-1">
<strong><small>{{this.title}}</small></strong>
</label>
<input type="text" class="form-control"
:class="{'is-invalid': this.showInvalidFeedback, 'is-valid': this.isValid}"
:id="this.uuid"
v-model="this.value"
@keydown="this.changed = true"
@blur="this.useValidation()"
:disabled="this.updating"
>
<div class="invalid-feedback">{{this.invalidFeedback}}</div>
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1"
v-if="warning"
>
<small><i class="bi bi-exclamation-triangle-fill me-2"></i><span v-html="warningText"></span></small>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,49 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "dashboardTheme",
setup(){
const dashboardConfigurationStore = DashboardConfigurationStore();
return {dashboardConfigurationStore}
},
methods: {
async switchTheme(value){
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server",
key: "dashboard_theme",
value: value
}, (res) => {
if (res.status){
this.dashboardConfigurationStore.Configuration.Server.dashboard_theme = value;
}
});
}
}
}
</script>
<template>
<div class="card mb-4 shadow rounded-3">
<p class="card-header">Dashboard Theme</p>
<div class="card-body d-flex gap-2">
<button class="btn bg-primary-subtle text-primary-emphasis flex-grow-1"
@click="this.switchTheme('light')"
:class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'light'}">
<i class="bi bi-sun-fill"></i>
Light
</button>
<button class="btn bg-primary-subtle text-primary-emphasis flex-grow-1"
@click="this.switchTheme('dark')"
:class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'dark'}">
<i class="bi bi-moon-fill"></i>
Dark
</button>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,85 @@
<script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js";
export default {
props:{
targetData: String,
title: String,
warning: false,
warningText: "",
},
setup(){
const store = DashboardConfigurationStore();
const uuid = `input_${v4()}`;
return {store, uuid};
},
data(){
return{
value:"",
invalidFeedback: "",
showInvalidFeedback: false,
isValid: false,
timeout: undefined,
changed: false,
updating: false,
}
},
mounted() {
this.value = this.store.Configuration.Peers[this.targetData];
},
methods:{
async useValidation(){
if(this.changed){
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Peers",
key: this.targetData,
value: this.value
}, (res) => {
if (res.status){
this.isValid = true;
this.showInvalidFeedback = false;
this.store.Configuration.Peers[this.targetData] = this.value
clearTimeout(this.timeout)
this.timeout = setTimeout(() => this.isValid = false, 5000);
}else{
this.isValid = false;
this.showInvalidFeedback = true;
this.invalidFeedback = res.message
}
this.changed = false
this.updating = false;
})
}
}
}
}
</script>
<template>
<div class="form-group mb-2">
<label :for="this.uuid" class="text-muted mb-1">
<strong><small>{{this.title}}</small></strong>
</label>
<input type="text" class="form-control"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
:id="this.uuid"
v-model="this.value"
@keydown="this.changed = true"
@blur="useValidation()"
:disabled="this.updating"
>
<div class="invalid-feedback">{{this.invalidFeedback}}</div>
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1"
v-if="warning"
>
<small><i class="bi bi-exclamation-triangle-fill me-2"></i><span v-html="warningText"></span></small>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,119 @@
<script>
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import QRCode from "qrcode";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "totp",
async setup(){
const store = DashboardConfigurationStore();
let l = ""
await fetchGet("/api/Welcome_GetTotpLink", {}, (res => {
if (res.status) l = res.data;
}));
return {l, store}
},
mounted() {
if (this.l) {
QRCode.toCanvas(document.getElementById('qrcode'), this.l, function (error) {})
}
},
data(){
return {
totp: "",
totpInvalidMessage: "",
verified: false
}
},
methods: {
validateTotp(){
}
},
watch: {
totp(newVal){
const input = document.querySelector("#totp");
input.classList.remove("is-invalid", "is-valid")
if (newVal.length === 6){
console.log(newVal)
if (/[0-9]{6}/.test(newVal)){
fetchPost("/api/Welcome_VerifyTotpLink", {
totp: newVal
}, (res) => {
if (res.status){
this.verified = true;
input.classList.add("is-valid");
this.$emit("verified")
}else{
input.classList.add("is-invalid")
this.totpInvalidMessage = "TOTP does not match."
}
})
}else{
input.classList.add("is-invalid");
this.totpInvalidMessage = "TOTP can only contain numbers"
}
}
}
}
}
</script>
<template>
<div class="container-fluid login-container-fluid d-flex main pt-5 overflow-scroll"
:data-bs-theme="this.store.Configuration.Server.dashboard_theme">
<div class="m-auto text-body" style="width: 500px">
<div class="d-flex flex-column">
<div>
<h1 class="dashboardLogo display-4">Multi-Factor Authentication</h1>
<p class="mb-2"><small class="text-muted">1. Please scan the following QR Code to generate TOTP</small></p>
<canvas id="qrcode" class="rounded-3 mb-2"></canvas>
<div class="p-3 bg-body-secondary rounded-3 border mb-3">
<p class="text-muted mb-0"><small>Or you can click the link below:</small>
</p><a :href="this.l"><code style="line-break: anywhere">{{this.l}}</code></a>
</div>
<label for="totp" class="mb-2"><small class="text-muted">2. Enter the TOTP generated by your authenticator to verify</small></label>
<div class="form-group mb-2">
<input class="form-control text-center totp"
id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code"
v-model="this.totp"
:disabled="this.verified"
>
<div class="invalid-feedback">
{{this.totpInvalidMessage}}
</div>
<div class="valid-feedback">
TOTP verified!
</div>
</div>
<div class="alert alert-warning rounded-3">
<i class="bi bi-exclamation-triangle-fill me-2"></i> If you ever lost your TOTP and can't login, please follow instruction on
<a href="https://github.com/donaldzou/WGDashboard" target="_blank">readme.md</a> to reset.
</div>
</div>
<hr>
<div class="d-flex gap-3 mt-5 flex-column">
<RouterLink
to="/"
v-if="!this.verified"
class="btn bg-secondary-subtle text-secondary-emphasis
rounded-3
flex-grow-1 btn-lg border-1 border-secondary-subtle shadow d-flex">
I don't need MFA <i class="bi bi-chevron-right ms-auto"></i>
</RouterLink>
<RouterLink
to="/"
v-else class="btn btn-dark btn-lg d-flex btn-brand shadow align-items-center flex-grow-1 rounded-3">
Complete <i class="bi bi-chevron-right ms-auto"></i>
</RouterLink>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,165 @@
<script>
import dayjs from "dayjs";
export default {
name: "RemoteServer",
props: {
server: Object
},
data(){
return{
active: false,
startTime: undefined,
endTime: undefined,
errorMsg: "",
refreshing: false
}
},
methods: {
async handshake(){
this.active = false;
if (this.server.host && this.server.apiKey){
this.refreshing = true;
this.startTime = undefined;
this.endTime = undefined;
this.startTime = dayjs()
await fetch(`${this.server.host}/api/handshake`, {
headers: {
"content-type": "application/json",
"wg-dashboard-apikey": this.server.apiKey
},
method: "GET",
signal: AbortSignal.timeout(5000)
}).then(res => {
if (res.status === 200){
return res.json()
}
throw new Error(res.statusText)
}).then(() => {
this.endTime = dayjs()
this.active = true;
}).catch((res) => {
this.active = false;
this.errorMsg = res;
});
this.refreshing = false;
}
},
async connect(){
await fetch(`${this.server.host}/api/authenticate`, {
headers: {
"content-type": "application/json",
"wg-dashboard-apikey": this.server.apiKey
},
body: JSON.stringify({
host: window.location.hostname
}),
method: "POST",
signal: AbortSignal.timeout(5000),
}).then(res => res.json()).then(res => {
this.$emit("setActiveServer")
this.$router.push('/')
})
}
},
mounted() {
this.handshake()
},
computed: {
getHandshakeTime(){
if (this.startTime && this.endTime){
return `${dayjs().subtract(this.startTime).millisecond()}ms`
}else{
if (this.refreshing){
return `Pinging...`
}
return this.errorMsg ? this.errorMsg : "N/A"
}
}
}
}
</script>
<template>
<div class="card rounded-3">
<div class="card-body">
<div class="d-flex gap-3 w-100 remoteServerContainer">
<div class="d-flex gap-3 align-items-center flex-grow-1">
<i class="bi bi-server"></i>
<input class="form-control form-control-sm"
@blur="this.handshake()"
v-model="this.server.host"
type="url">
</div>
<div class="d-flex gap-3 align-items-center flex-grow-1">
<i class="bi bi-key-fill"></i>
<input class="form-control form-control-sm"
@blur="this.handshake()"
v-model="this.server.apiKey"
type="text">
</div>
<div class="d-flex gap-2 button-group">
<button
@click="this.$emit('delete')"
class="ms-auto btn btn-sm bg-danger-subtle text-danger-emphasis border-1 border-danger-subtle">
<i class="bi bi-trash"></i>
</button>
<button
@click="this.connect()"
:class="{disabled: !this.active}"
class="ms-auto btn btn-sm bg-success-subtle text-success-emphasis border-1 border-success-subtle">
<i class="bi bi-arrow-right-circle"></i>
</button>
</div>
</div>
</div>
<div class="card-footer gap-2 d-flex align-items-center">
<span class="dot ms-0 me-2" :class="[this.active ? 'active':'inactive']"></span>
<small>{{this.getHandshakeTime}}</small>
<div class="spin ms-auto text-primary-emphasis" v-if="this.refreshing">
<i class="bi bi-arrow-clockwise"></i>
</div>
<a role="button"
v-else
@click="this.handshake()"
class="text-primary-emphasis text-decoration-none ms-auto disabled">
<i class="bi bi-arrow-clockwise me"></i>
</a>
</div>
</div>
</template>
<style scoped>
.dot.inactive{
background-color: #dc3545;
box-shadow: 0 0 0 0.2rem #dc354545;
}
.spin{
animation: spin 1s infinite cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
@keyframes spin {
0%{
transform: rotate(0deg);
}
100%{
transform: rotate(360deg);
}
}
@media screen and (max-width: 768px) {
.remoteServerContainer{
flex-direction: column;
}
.remoteServerContainer .button-group button{
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,40 @@
<script>
import RemoteServer from "@/components/signInComponents/RemoteServer.vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "RemoteServerList",
setup(){
const store = DashboardConfigurationStore();
return {store}
},
components: {RemoteServer}
}
</script>
<template>
<div class="w-100 mt-3">
<div class="d-flex align-items-center mb-3">
<h5 class="mb-0">Server List</h5>
<button
@click="this.store.addCrossServerConfiguration()"
class="btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle shadow-sm ms-auto">
<i class="bi bi-plus-circle-fill me-2"></i>Server
</button>
</div>
<div class="w-100 d-flex gap-3 flex-column p-3 border border-1 border-secondary-subtle rounded-3"
style="height: 400px; overflow-y: scroll">
<RemoteServer v-for="(server, key) in this.store.CrossServerConfiguration.ServerList"
@setActiveServer="this.store.setActiveCrossServer(key)"
@delete="this.store.deleteCrossServerConfiguration(key)"
:key="key"
:server="server"></RemoteServer>
<h6 class="text-muted m-auto" v-if="Object.keys(this.store.CrossServerConfiguration.ServerList).length === 0">
Click<i class="bi bi-plus-circle-fill mx-1"></i>to add your server</h6>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,26 @@
import '../../css/dashboard.css'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap/dist/js/bootstrap.js'
import 'bootstrap-icons/font/bootstrap-icons.css'
import 'animate.css/animate.compat.css'
import '@vuepic/vue-datepicker/dist/main.css'
import {createApp, markRaw} from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
const pinia = createPinia();
pinia.use(({ store }) => {
store.$router = markRaw(router)
})
app.use(pinia)
app.mount('#app')

View File

@@ -0,0 +1,18 @@
import {fetchGet} from "@/utilities/fetch.js";
export class WireguardConfigurations{
Configurations = undefined;
constructor() {
this.Configurations = undefined
}
async initialization(){
await this.getConfigurations()
}
async getConfigurations(){
await fetchGet("/api/getWireguardConfigurations", {}, (res) => {
if (res.status) this.Configurations = res.data
});
}
}

View File

@@ -0,0 +1,166 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import {cookie} from "../utilities/cookie.js";
import Index from "@/views/index.vue"
import Signin from "@/views/signin.vue";
import ConfigurationList from "@/components/configurationList.vue";
import {fetchGet} from "@/utilities/fetch.js";
import Settings from "@/views/settings.vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import Setup from "@/views/setup.vue";
import NewConfiguration from "@/views/newConfiguration.vue";
import Configuration from "@/views/configuration.vue";
import PeerList from "@/components/configurationComponents/peerList.vue";
import PeerCreate from "@/components/configurationComponents/peerCreate.vue";
import Ping from "@/views/ping.vue";
import Traceroute from "@/views/traceroute.vue";
import Totp from "@/components/setupComponent/totp.vue";
import Share from "@/views/share.vue";
const checkAuth = async () => {
let result = false
await fetchGet("/api/validateAuthentication", {}, (res) => {
result = res.status
});
return result;
}
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
name: "Index",
path: '/',
component: Index,
meta: {
requiresAuth: true,
},
children: [
{
name: "Configuration List",
path: '',
component: ConfigurationList,
meta: {
title: "WireGuard Configurations"
}
},
{
name: "Settings",
path: '/settings',
component: Settings,
meta: {
title: "Settings"
}
},
{
path: '/ping',
name: "Ping",
component: Ping,
},
{
path: '/traceroute',
name: "Traceroute",
component: Traceroute,
},
{
name: "New Configuration",
path: '/new_configuration',
component: NewConfiguration,
meta: {
title: "New Configuration"
}
},
{
name: "Configuration",
path: '/configuration/:id',
component: Configuration,
meta: {
title: "Configuration"
},
children: [
{
name: "Peers List",
path: 'peers',
component: PeerList
},
{
name: "Peers Create",
path: 'create',
component: PeerCreate
},
]
},
]
},
{
path: '/signin', component: Signin,
meta: {
title: "Sign In"
}
},
{
path: '/welcome', component: Setup,
meta: {
requiresAuth: true,
title: "Welcome to WGDashboard"
},
},
{
path: '/2FASetup', component: Totp,
meta: {
requiresAuth: true,
title: "Multi-Factor Authentication Setup"
},
},
{
path: '/share', component: Share,
meta: {
title: "Share"
}
}
]
});
router.beforeEach(async (to, from, next) => {
const wireguardConfigurationsStore = WireguardConfigurationsStore();
const dashboardConfigurationStore = DashboardConfigurationStore();
if (to.meta.title){
if (to.params.id){
document.title = to.params.id + " | WGDashboard";
}else{
document.title = to.meta.title + " | WGDashboard";
}
}else{
document.title = "WGDashboard"
}
dashboardConfigurationStore.ShowNavBar = false;
if (to.meta.requiresAuth){
if (!dashboardConfigurationStore.getActiveCrossServer()){
if (cookie.getCookie("authToken") && await checkAuth()){
await dashboardConfigurationStore.getConfiguration()
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
await wireguardConfigurationsStore.getConfigurations();
}
dashboardConfigurationStore.Redirect = undefined;
next()
}else{
dashboardConfigurationStore.Redirect = to;
next("/signin")
dashboardConfigurationStore.newMessage("WGDashboard", "Session Ended", "warning")
}
}else{
await dashboardConfigurationStore.getConfiguration()
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
await wireguardConfigurationsStore.getConfigurations();
}
next()
}
}else {
next();
}
});
export default router

View File

@@ -0,0 +1,89 @@
import {defineStore} from "pinia";
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {v4} from "uuid";
export const DashboardConfigurationStore = defineStore('DashboardConfigurationStore', {
state: () => ({
Redirect: undefined,
Configuration: undefined,
Messages: [],
Peers: {
Selecting: false,
RefreshInterval: undefined
},
CrossServerConfiguration:{
Enable: false,
ServerList: {}
},
ActiveServerConfiguration: undefined,
IsElectronApp: false,
ShowNavBar: false
}),
actions: {
initCrossServerConfiguration(){
const currentConfiguration = localStorage.getItem('CrossServerConfiguration');
if (localStorage.getItem("ActiveCrossServerConfiguration") !== null){
this.ActiveServerConfiguration = localStorage.getItem("ActiveCrossServerConfiguration");
}
if (currentConfiguration === null){
window.localStorage.setItem('CrossServerConfiguration', JSON.stringify(this.CrossServerConfiguration))
}else{
this.CrossServerConfiguration = JSON.parse(currentConfiguration)
}
},
syncCrossServerConfiguration(){
window.localStorage.setItem('CrossServerConfiguration', JSON.stringify(this.CrossServerConfiguration))
},
addCrossServerConfiguration(){
this.CrossServerConfiguration.ServerList[v4().toString()] = {host: "", apiKey: "", active: false}
},
deleteCrossServerConfiguration(key){
delete this.CrossServerConfiguration.ServerList[key];
},
getActiveCrossServer(){
const key = localStorage.getItem('ActiveCrossServerConfiguration');
if (key !== null){
return this.CrossServerConfiguration.ServerList[key]
}
return undefined
},
setActiveCrossServer(key){
this.ActiveServerConfiguration = key;
localStorage.setItem('ActiveCrossServerConfiguration', key)
},
removeActiveCrossServer(){
this.ActiveServerConfiguration = undefined;
localStorage.removeItem('ActiveCrossServerConfiguration')
},
async getConfiguration(){
await fetchGet("/api/getDashboardConfiguration", {}, (res) => {
if (res.status) this.Configuration = res.data
});
},
// async updateConfiguration(){
// await fetchPost("/api/updateDashboardConfiguration", {
// DashboardConfiguration: this.Configuration
// }, (res) => {
// console.log(res)
// })
// },
async signOut(){
await fetchGet("/api/signout", {}, (res) => {
this.removeActiveCrossServer();
this.$router.go('/signin')
});
},
newMessage(from, content, type){
this.Messages.push({
id: v4(),
from: from,
content: content,
type: type,
show: true
})
}
}
});

View File

@@ -0,0 +1,83 @@
import {defineStore} from "pinia";
import {fetchGet} from "@/utilities/fetch.js";
import isCidr from "is-cidr";
export const WireguardConfigurationsStore = defineStore('WireguardConfigurationsStore', {
state: () => ({
Configurations: undefined,
searchString: "",
ConfigurationListInterval: undefined,
PeerScheduleJobs: {
dropdowns: {
Field: [
{
display: "Total Received",
value: "total_receive",
unit: "GB",
type: 'number'
},
{
display: "Total Sent",
value: "total_sent",
unit: "GB",
type: 'number'
},
{
display: "Total Data",
value: "total_data",
unit: "GB",
type: 'number'
},
{
display: "Date",
value: "date",
type: 'date'
}
],
Operator: [
{
display: "equal",
value: "eq"
},
{
display: "not equal",
value: "neq"
},
{
display: "larger than",
value: "lgt"
},
{
display: "less than",
value: "lst"
},
],
Action: [
{
display: "Restrict Peer",
value: "restrict"
},
{
display: "Delete Peer",
value: "delete"
}
]
}
}
}),
actions: {
async getConfigurations(){
await fetchGet("/api/getWireguardConfigurations", {}, (res) => {
if (res.status) this.Configurations = res.data
});
},
regexCheckIP(ip){
let regex = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/;
return regex.test(ip)
},
checkCIDR(ip){
return isCidr(ip) !== 0
},
}
});

View File

@@ -0,0 +1,20 @@
import {defineStore} from "pinia";
import {fetchGet} from "@/utilities/fetch.js";
export const wgdashboardStore = defineStore('WGDashboardStore', {
state: () => ({
WireguardConfigurations: undefined,
DashboardConfiguration: undefined
}),
actions: {
async getDashboardConfiguration(){
await fetchGet("/api/getDashboardConfiguration", {}, (res) => {
console.log(res.status)
if (res.status) this.DashboardConfiguration = res.data
})
}
}
});

View File

@@ -0,0 +1,17 @@
import subprocess
def _generateKeyPairs(amount: int) -> list[list[str]] | None:
try:
pairs = subprocess.check_output(
f'''for ((i = 0 ; i<{amount} ; i++ ));do privateKey=$(wg genkey) presharedKey=$(wg genkey) publicKey=$(wg pubkey <<< "$privateKey") echo "$privateKey,$publicKey,$presharedKey"; done''', shell=True, stderr=subprocess.STDOUT
)
pairs = pairs.decode().split("\n")
print(pairs)
return [x.split(",") for x in pairs]
except subprocess.CalledProcessError as exp:
print(str(exp))
return []
_generateKeyPairs(20)

View File

@@ -0,0 +1,9 @@
export const cookie = {
//https://stackoverflow.com/a/15724300
getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
}

View File

@@ -0,0 +1,75 @@
import router from "@/router/index.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
const getHeaders = () => {
let headers = {
"content-type": "application/json"
}
const store = DashboardConfigurationStore();
const apiKey = store.getActiveCrossServer();
if (apiKey){
headers['wg-dashboard-apikey'] = apiKey.apiKey
}
return headers
}
const getUrl = (url) => {
const store = DashboardConfigurationStore();
const apiKey = store.getActiveCrossServer();
if (apiKey){
return `${apiKey.host}${url}`
}
// console.log("URL fetching: ", import.meta.env.MODE === 'development' ? url
// : `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}`)
return import.meta.env.MODE === 'development' ? url
: `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}`
}
export const fetchGet = async (url, params=undefined, callback=undefined) => {
const urlSearchParams = new URLSearchParams(params);
await fetch(`${getUrl(url)}?${urlSearchParams.toString()}`, {
headers: getHeaders()
})
.then((x) => {
const store = DashboardConfigurationStore();
if (!x.ok){
if (x.status !== 200){
if (x.status === 401){
store.newMessage("WGDashboard", "Session Ended", "warning")
}
throw new Error(x.statusText)
}
}else{
return x.json()
}
}).then(x => callback ? callback(x) : undefined).catch(x => {
console.log(x)
router.push({path: '/signin'})
})
}
export const fetchPost = async (url, body, callback) => {
await fetch(`${getUrl(url)}`, {
headers: getHeaders(),
method: "POST",
body: JSON.stringify(body)
}).then((x) => {
const store = DashboardConfigurationStore();
if (!x.ok){
if (x.status !== 200){
if (x.status === 401){
store.newMessage("WGDashboard", "Session Ended", "warning")
}
throw new Error(x.statusText)
}
}else{
return x.json()
}
}).then(x => callback ? callback(x) : undefined).catch(x => {
console.log(x)
router.push({path: '/signin'})
})
}

View File

@@ -0,0 +1,3 @@
export const ipV46RegexCheck = (input) => {
}

View File

@@ -0,0 +1,313 @@
/*! SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
(function() {
function gf(init) {
var r = new Float64Array(16);
if (init) {
for (var i = 0; i < init.length; ++i)
r[i] = init[i];
}
return r;
}
function pack(o, n) {
var b, m = gf(), t = gf();
for (var i = 0; i < 16; ++i)
t[i] = n[i];
carry(t);
carry(t);
carry(t);
for (var j = 0; j < 2; ++j) {
m[0] = t[0] - 0xffed;
for (var i = 1; i < 15; ++i) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
cswap(t, m, 1 - b);
}
for (var i = 0; i < 16; ++i) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
function carry(o) {
var c;
for (var i = 0; i < 16; ++i) {
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
o[i] &= 0xffff;
}
}
function cswap(p, q, b) {
var t, c = ~(b - 1);
for (var i = 0; i < 16; ++i) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
function add(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] + b[i]) | 0;
}
function subtract(o, a, b) {
for (var i = 0; i < 16; ++i)
o[i] = (a[i] - b[i]) | 0;
}
function multmod(o, a, b) {
var t = new Float64Array(31);
for (var i = 0; i < 16; ++i) {
for (var j = 0; j < 16; ++j)
t[i + j] += a[i] * b[j];
}
for (var i = 0; i < 15; ++i)
t[i] += 38 * t[i + 16];
for (var i = 0; i < 16; ++i)
o[i] = t[i];
carry(o);
carry(o);
}
function invert(o, i) {
var c = gf();
for (var a = 0; a < 16; ++a)
c[a] = i[a];
for (var a = 253; a >= 0; --a) {
multmod(c, c, c);
if (a !== 2 && a !== 4)
multmod(c, c, i);
}
for (var a = 0; a < 16; ++a)
o[a] = c[a];
}
function clamp(z) {
z[31] = (z[31] & 127) | 64;
z[0] &= 248;
}
function generatePublicKey(privateKey) {
var r, z = new Uint8Array(32);
var a = gf([1]),
b = gf([9]),
c = gf(),
d = gf([1]),
e = gf(),
f = gf(),
_121665 = gf([0xdb41, 1]),
_9 = gf([9]);
for (var i = 0; i < 32; ++i)
z[i] = privateKey[i];
clamp(z);
for (var i = 254; i >= 0; --i) {
r = (z[i >>> 3] >>> (i & 7)) & 1;
cswap(a, b, r);
cswap(c, d, r);
add(e, a, c);
subtract(a, a, c);
add(c, b, d);
subtract(b, b, d);
multmod(d, e, e);
multmod(f, a, a);
multmod(a, c, a);
multmod(c, b, e);
add(e, a, c);
subtract(a, a, c);
multmod(b, a, a);
subtract(c, d, f);
multmod(a, c, _121665);
add(a, a, d);
multmod(c, c, a);
multmod(a, d, f);
multmod(d, b, _9);
multmod(b, e, e);
cswap(a, b, r);
cswap(c, d, r);
}
invert(c, c);
multmod(a, a, c);
pack(z, a);
return z;
}
function generatePresharedKey() {
var privateKey = new Uint8Array(32);
window.crypto.getRandomValues(privateKey);
return privateKey;
}
function generatePrivateKey() {
var privateKey = generatePresharedKey();
clamp(privateKey);
return privateKey;
}
function encodeBase64(dest, src) {
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
for (var i = 0; i < 4; ++i)
dest[i] = input[i] + 65 +
(((25 - input[i]) >> 8) & 6) -
(((51 - input[i]) >> 8) & 75) -
(((61 - input[i]) >> 8) & 15) +
(((62 - input[i]) >> 8) & 3);
}
function keyToBase64(key) {
var i, base64 = new Uint8Array(44);
for (i = 0; i < 32 / 3; ++i)
encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
base64[43] = 61;
return String.fromCharCode.apply(null, base64);
}
function base64ToKey(base64) {
let binary_string = window.atob(base64);
let len = binary_string.length;
let bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
let uint8 = new Uint8Array(bytes.buffer);
return uint8;
}
function putU32(b, n)
{
b.push(n & 0xff, (n >>> 8) & 0xff, (n >>> 16) & 0xff, (n >>> 24) & 0xff);
}
function putU16(b, n)
{
b.push(n & 0xff, (n >>> 8) & 0xff);
}
function putBytes(b, a)
{
for (var i = 0; i < a.length; ++i)
b.push(a[i] & 0xff);
}
function encodeString(s)
{
var utf8 = unescape(encodeURIComponent(s));
var b = new Uint8Array(utf8.length);
for (var i = 0; i < utf8.length; ++i)
b[i] = utf8.charCodeAt(i);
return b;
}
function crc32(b)
{
if (!crc32.table) {
crc32.table = [];
for (var c = 0, n = 0; n < 256; c = ++n) {
for (var k = 0; k < 8; ++k)
c = ((c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1));
crc32.table[n] = c;
}
}
var crc = -1;
for (var i = 0; i < b.length; ++i)
crc = (crc >>> 8) ^ crc32.table[(crc ^ b[i]) & 0xff];
return (crc ^ (-1)) >>> 0;
}
function createZipFile(files)
{
var b = [];
var cd = [];
var offset = 0;
for (var i = 0; i < files.length; ++i) {
var name = encodeString(files[i].fileName);
var contents = encodeString(files[i].file);
var crc = crc32(contents);
putU32(b, 0x04034b50); /* signature */
putU16(b, 20); /* version needed */
putU16(b, 0); /* flags */
putU16(b, 0); /* compression method */
putU16(b, 0); /* mtime */
putU16(b, 0); /* mdate */
putU32(b, crc); /* crc32 */
putU32(b, contents.length); /* compressed size */
putU32(b, contents.length); /* uncompressed size */
putU16(b, name.length); /* file name length */
putU16(b, 0); /* extra field length */
putBytes(b, name);
putBytes(b, contents);
putU32(cd, 0x02014b50); /* signature */
putU16(cd, 0); /* version made */
putU16(cd, 20); /* version needed */
putU16(cd, 0); /* flags */
putU16(cd, 0); /* compression method */
putU16(cd, 0); /* mtime */
putU16(cd, 0); /* mdate */
putU32(cd, crc); /* crc32 */
putU32(cd, contents.length); /* compressed size */
putU32(cd, contents.length); /* uncompressed size */
putU16(cd, name.length); /* file name length */
putU16(cd, 0); /* extra field length */
putU16(cd, 0); /* file comment length */
putU16(cd, 0); /* disk number start */
putU16(cd, 0); /* internal file attributes */
putU32(cd, 32); /* external file attributes - 'archive' bit set (32) */
putU32(cd, offset); /* relative offset of local header */
putBytes(cd, name); /* file name */
offset += 30 + contents.length + name.length
}
putBytes(b, cd); /* central directory */
putU32(b, 0x06054b50); /* end of central directory signature */
putU16(b, 0); /* number of this disk */
putU16(b, 0); /* number of disk with central directory start */
putU16(b, files.length); /* number of entries on disk */
putU16(b, files.length); /* number of entries */
putU32(b, cd.length); /* length of central directory */
putU32(b, offset); /* offset to start of central directory */
putU16(b, 0); /* zip comment size */
return Uint8Array.from(b);
}
window.wireguard = {
generateKeypair: function() {
var privateKey = generatePrivateKey();
var publicKey = generatePublicKey(privateKey);
var presharedKey = generatePresharedKey();
return {
publicKey: keyToBase64(publicKey),
privateKey: keyToBase64(privateKey),
presharedKey: keyToBase64(presharedKey)
};
},
generatePublicKey: function (privateKey){
privateKey = base64ToKey(privateKey);
return keyToBase64(generatePublicKey(privateKey));
},
generateZipFiles: function(res, zipFileName){
var files = res.data;
var zipFile = createZipFile(files);
var blob = new Blob([zipFile], { type: "application/zip" });
var a = document.createElement("a");
a.download = zipFileName;
a.href = URL.createObjectURL(blob);
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
};
})();

View File

@@ -0,0 +1,21 @@
<script>
export default {
name: "configuration",
}
</script>
<template>
<div class="mt-md-5 mt-3 text-body">
<RouterView v-slot="{ Component, route }">
<Transition name="fade2" mode="out-in">
<Suspense>
<Component :is="Component" :key="route.path"></Component>
</Suspense>
</Transition>
</RouterView>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,51 @@
<script>
import Navbar from "@/components/navbar.vue";
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
import {WireguardConfigurations} from "@/models/WireguardConfigurations.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import Message from "@/components/messageCentreComponent/message.vue";
export default {
name: "index",
components: {Message, Navbar},
async setup(){
const dashboardConfigurationStore = DashboardConfigurationStore()
return {dashboardConfigurationStore}
},
computed: {
getMessages(){
return this.dashboardConfigurationStore.Messages.filter(x => x.show)
}
}
}
</script>
<template>
<div class="container-fluid flex-grow-1 main" :data-bs-theme="this.dashboardConfigurationStore.Configuration.Server.dashboard_theme">
<div class="row h-100">
<Navbar></Navbar>
<main class="col-md-9 ml-sm-auto col-lg-10 px-md-4 overflow-y-scroll mb-0" style="height: calc(100vh - 50px)">
<Suspense>
<RouterView v-slot="{Component}">
<Transition name="fade2" mode="out-in">
<Component :is="Component"></Component>
</Transition>
</RouterView>
</Suspense>
<div class="messageCentre text-body position-fixed">
<TransitionGroup name="message" tag="div" class="position-relative">
<Message v-for="m in getMessages.slice().reverse()"
:message="m" :key="m.id"></Message>
</TransitionGroup>
</div>
</main>
</div>
</div>
</template>
<style scoped>
.messageCentre{
top: calc(50px + 1rem);
right: 1rem;
}
</style>

View File

@@ -0,0 +1,295 @@
<script>
import {parse} from "cidr-tools";
import '@/utilities/wireguard.js'
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "newConfiguration",
setup(){
const store = WireguardConfigurationsStore()
return {store}
},
data(){
return {
newConfiguration: {
ConfigurationName: "",
Address: "",
ListenPort: "",
PrivateKey: "",
PublicKey: "",
PresharedKey: "",
PreUp: "",
PreDown: "",
PostUp: "",
PostDown: ""
},
numberOfAvailableIPs: "0",
error: false,
errorMessage: "",
success: false,
loading: false
}
},
created() {
this.wireguardGenerateKeypair();
},
methods: {
wireguardGenerateKeypair(){
const wg = window.wireguard.generateKeypair();
this.newConfiguration.PrivateKey = wg.privateKey;
this.newConfiguration.PublicKey = wg.publicKey;
this.newConfiguration.PresharedKey = wg.presharedKey;
},
async saveNewConfiguration(){
if (this.goodToSubmit){
this.loading = true;
await fetchPost("/api/addWireguardConfiguration", this.newConfiguration, async (res) => {
if (res.status){
this.success = true
await this.store.getConfigurations()
setTimeout(() => {
this.$router.push('/')
}, 1000)
}else{
this.error = true;
this.errorMessage = res.message;
document.querySelector(`#${res.data}`).classList.remove("is-valid")
document.querySelector(`#${res.data}`).classList.add("is-invalid")
this.loading = false;
}
})
}
}
},
computed: {
goodToSubmit(){
let requirements = ["ConfigurationName", "Address", "ListenPort", "PrivateKey"]
let elements = [...document.querySelectorAll("input[required]")];
return requirements.find(x => {
return this.newConfiguration[x].length === 0
}) === undefined && elements.find(x => {
return x.classList.contains("is-invalid")
}) === undefined
}
},
watch: {
'newConfiguration.Address'(newVal){
let ele = document.querySelector("#Address");
ele.classList.remove("is-invalid", "is-valid")
try{
if (newVal.trim().split("/").filter(x => x.length > 0).length !== 2){
throw Error()
}
let p = parse(newVal);
let i = p.end - p.start;
this.numberOfAvailableIPs = i.toLocaleString();
ele.classList.add("is-valid")
}catch (e) {
this.numberOfAvailableIPs = "0";
ele.classList.add("is-invalid")
}
},
'newConfiguration.ListenPort'(newVal){
let ele = document.querySelector("#ListenPort");
ele.classList.remove("is-invalid", "is-valid")
if (newVal < 0 || newVal > 65353 || !Number.isInteger(newVal)){
ele.classList.add("is-invalid")
}else{
ele.classList.add("is-valid")
}
},
'newConfiguration.ConfigurationName'(newVal){
let ele = document.querySelector("#ConfigurationName");
ele.classList.remove("is-invalid", "is-valid")
if (!/^[a-zA-Z0-9_=+.-]{1,15}$/.test(newVal) || newVal.length === 0 || this.store.Configurations.find(x => x.Name === newVal)){
ele.classList.add("is-invalid")
}else{
ele.classList.add("is-valid")
}
},
'newConfiguration.PrivateKey'(newVal){
let ele = document.querySelector("#PrivateKey");
ele.classList.remove("is-invalid", "is-valid")
try{
wireguard.generatePublicKey(newVal)
ele.classList.add("is-valid")
}catch (e) {
ele.classList.add("is-invalid")
}
}
}
}
</script>
<template>
<div class="mt-5">
<div class="container mb-4">
<div class="mb-4 d-flex align-items-center gap-4">
<RouterLink to="/" class="text-decoration-none">
<h3 class="mb-0 text-body">
<i class="bi bi-chevron-left me-4"></i> New Configuration
</h3>
</RouterLink>
</div>
<form class="text-body d-flex flex-column gap-3"
@submit="(e) => {e.preventDefault(); this.saveNewConfiguration();}"
>
<div class="card rounded-3 shadow">
<div class="card-header">Configuration Name</div>
<div class="card-body">
<input type="text" class="form-control" placeholder="ex. wg1" id="ConfigurationName"
v-model="this.newConfiguration.ConfigurationName"
:disabled="this.loading"
required>
<div class="invalid-feedback">
<div v-if="this.error">{{this.errorMessage}}</div>
<div v-else>
Configuration name is invalid. Possible reasons:
<ul class="mb-0">
<li>Configuration name already exist.</li>
<li>Configuration name can only contain 15 lower/uppercase alphabet, numbers, "_"(underscore), "="(equal), "+"(plus), "."(period/dot), "-"(dash/hyphen)</li>
</ul>
</div>
</div>
</div>
</div>
<div class="card rounded-3 shadow">
<div class="card-header">Private Key / Public Key / Pre-Shared Key</div>
<div class="card-body" style="font-family: var(--bs-font-monospace)">
<div class="mb-2">
<label class="text-muted fw-bold mb-1"><small>PRIVATE KEY</small></label>
<div class="input-group">
<input type="text" class="form-control" id="PrivateKey" required
:disabled="this.loading"
v-model="this.newConfiguration.PrivateKey" disabled
>
<button class="btn btn-outline-primary" type="button"
title="Regenerate Private Key"
@click="wireguardGenerateKeypair()"
>
<i class="bi bi-arrow-repeat"></i>
</button>
</div>
</div>
<div>
<label class="text-muted fw-bold mb-1"><small>PUBLIC KEY</small></label>
<input type="text" class="form-control" id="PublicKey"
v-model="this.newConfiguration.PublicKey" disabled
>
</div>
</div>
</div>
<div class="card rounded-3 shadow">
<div class="card-header">Listen Port</div>
<div class="card-body">
<input type="number" class="form-control" placeholder="0-65353" id="ListenPort"
min="1"
max="65353"
v-model="this.newConfiguration.ListenPort"
:disabled="this.loading"
required>
<div class="invalid-feedback">
<div v-if="this.error">{{this.errorMessage}}</div>
<div v-else>
Invalid port
</div>
</div>
</div>
</div>
<div class="card rounded-3 shadow">
<div class="card-header d-flex align-items-center">
IP Address & Range
<span class="badge rounded-pill text-bg-success ms-auto">{{ numberOfAvailableIPs }} Available IPs</span>
</div>
<div class="card-body">
<input type="text" class="form-control"
placeholder="Ex: 10.0.0.1/24" id="Address"
v-model="this.newConfiguration.Address"
:disabled="this.loading"
required>
<div class="invalid-feedback">
<div v-if="this.error">{{this.errorMessage}}</div>
<div v-else>
IP address & range is invalid.
</div>
</div>
</div>
</div>
<hr>
<div class="accordion" id="newConfigurationOptionalAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#newConfigurationOptionalAccordionCollapse">
Optional Settings
</button>
</h2>
<div id="newConfigurationOptionalAccordionCollapse"
class="accordion-collapse collapse" data-bs-parent="#newConfigurationOptionalAccordion">
<div class="accordion-body d-flex flex-column gap-3">
<div class="card rounded-3">
<div class="card-header">PreUp</div>
<div class="card-body">
<input type="text" class="form-control" id="preUp" v-model="this.newConfiguration.PreUp">
</div>
</div>
<div class="card rounded-3">
<div class="card-header">PreDown</div>
<div class="card-body">
<input type="text" class="form-control" id="preDown" v-model="this.newConfiguration.PreDown">
</div>
</div>
<div class="card rounded-3">
<div class="card-header">PostUp</div>
<div class="card-body">
<input type="text" class="form-control" id="postUp" v-model="this.newConfiguration.PostUp">
</div>
</div>
<div class="card rounded-3">
<div class="card-header">PostDown</div>
<div class="card-body">
<input type="text" class="form-control" id="postDown" v-model="this.newConfiguration.PostDown">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- <RouterLink to="/new_configuration" class="btn btn-success rounded-3 shadow ms-auto rounded-3">-->
<!-- <i class="bi bi-save me-2"></i>-->
<!-- Save-->
<!-- </RouterLink>-->
<button class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto"
:disabled="!this.goodToSubmit">
<span v-if="this.success" class="d-flex w-100">
Success! <i class="bi bi-check-circle-fill ms-2"></i>
</span>
<span v-else-if="!this.loading" class="d-flex w-100">
Save Configuration <i class="bi bi-save-fill ms-2"></i>
</span>
<span v-else class="d-flex w-100 align-items-center">
Saving...
<span class="ms-2 spinner-border spinner-border-sm" role="status">
<!-- <span class="visually-hidden">Loading...</span>-->
</span>
</span>
</button>
</form>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,197 @@
<script>
import {fetchGet} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "ping",
data(){
return {
loading: false,
cips: {},
selectedConfiguration: undefined,
selectedPeer: undefined,
selectedIp: undefined,
count: 4,
pingResult: undefined,
pinging: false
}
},
setup(){
const store = DashboardConfigurationStore();
return {store}
},
mounted() {
fetchGet("/api/ping/getAllPeersIpAddress", {}, (res)=> {
if (res.status){
this.loading = true;
this.cips = res.data;
console.log(this.cips)
}
});
},
methods: {
execute(){
if (this.selectedIp){
this.pinging = true;
this.pingResult = undefined
fetchGet("/api/ping/execute", {
ipAddress: this.selectedIp,
count: this.count
}, (res) => {
if (res.status){
this.pingResult = res.data
}else{
this.store.newMessage("Server", res.message, "danger")
}
})
}
{} }
},
watch: {
selectedConfiguration(){
this.selectedPeer = undefined;
this.selectedIp = undefined;
},
selectedPeer(){
this.selectedIp = undefined;
}
}
}
</script>
<template>
<div class="mt-md-5 mt-3 text-body">
<div class="container">
<h3 class="mb-3 text-body">Ping</h3>
<div class="row">
<div class="col-sm-4 d-flex gap-2 flex-column">
<div>
<label class="mb-1 text-muted" for="configuration">
<small>Configuration</small></label>
<select class="form-select" v-model="this.selectedConfiguration">
<option disabled selected :value="undefined">Select a Configuration...</option>
<option :value="key" v-for="(val, key) in this.cips">
{{key}}
</option>
</select>
</div>
<div>
<label class="mb-1 text-muted" for="peer">
<small>Peer</small></label>
<select id="peer" class="form-select" v-model="this.selectedPeer" :disabled="this.selectedConfiguration === undefined">
<option disabled selected :value="undefined">Select a Peer...</option>
<option v-if="this.selectedConfiguration !== undefined" :value="key" v-for="(peer, key) in
this.cips[this.selectedConfiguration]">
{{key}}
</option>
</select>
</div>
<div>
<label class="mb-1 text-muted" for="ip">
<small>IP Address</small></label>
<select id="ip" class="form-select" v-model="this.selectedIp" :disabled="this.selectedPeer === undefined">
<option disabled selected :value="undefined">Select a IP...</option>
<option
v-if="this.selectedPeer !== undefined"
v-for="ip in this.cips[this.selectedConfiguration][this.selectedPeer].allowed_ips">
{{ip}}
</option>
</select>
</div>
<div>
<label class="mb-1 text-muted" for="count">
<small>Ping Count</small></label>
<input class="form-control" type="number"
v-model="this.count"
min="1" id="count" placeholder="How many times you want to ping?">
</div>
<button class="btn btn-primary rounded-3 mt-3"
:disabled="!this.selectedIp"
@click="this.execute()">
<i class="bi bi-person-walking me-2"></i>Go!
</button>
</div>
<div class="col-sm-8">
<TransitionGroup name="ping">
<div v-if="!this.pingResult" key="pingPlaceholder">
<div class="pingPlaceholder bg-body-secondary rounded-3 mb-3"
:class="{'animate__animated animate__flash animate__slower animate__infinite': this.pinging}"
:style="{'animation-delay': `${x*0.15}s`}"
v-for="x in 4" ></div>
</div>
<div v-else key="pingResult" class="d-flex flex-column gap-2 w-100">
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.15s">
<div class="card-body">
<p class="mb-0 text-muted"><small>Address</small></p>
{{this.pingResult.address}}
</div>
</div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.3s">
<div class="card-body">
<p class="mb-0 text-muted"><small>Is Alive</small></p>
<span :class="[this.pingResult.is_alive ? 'text-success':'text-danger']">
<i class="bi me-1"
:class="[this.pingResult.is_alive ? 'bi-check-circle-fill' : 'bi-x-circle-fill']"></i>
{{this.pingResult.is_alive ? "Yes": "No"}}
</span>
</div>
</div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.45s">
<div class="card-body">
<p class="mb-0 text-muted"><small>Average / Min / Max Round Trip Time</small></p>
<samp>{{this.pingResult.avg_rtt}}ms /
{{this.pingResult.min_rtt}}ms /
{{this.pingResult.max_rtt}}ms
</samp>
</div>
</div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.6s">
<div class="card-body">
<p class="mb-0 text-muted"><small>Sent / Received / Lost Package</small></p>
<samp>{{this.pingResult.package_sent}} /
{{this.pingResult.package_received}} /
{{this.pingResult.package_loss}}
</samp>
</div>
</div>
</div>
</TransitionGroup>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.pingPlaceholder{
width: 100%;
height: 79.98px;
}
.ping-move, /* apply transition to moving elements */
.ping-enter-active,
.ping-leave-active {
transition: all 0.4s cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
.ping-leave-active{
position: absolute;
}
.ping-enter-from,
.ping-leave-to {
opacity: 0;
//transform: scale(0.9);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.ping-leave-active {
position: absolute;
}
</style>

View File

@@ -0,0 +1,91 @@
<script>
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import PeersDefaultSettingsInput from "@/components/settingsComponent/peersDefaultSettingsInput.vue";
import {ipV46RegexCheck} from "@/utilities/ipCheck.js";
import AccountSettingsInputUsername from "@/components/settingsComponent/accountSettingsInputUsername.vue";
import AccountSettingsInputPassword from "@/components/settingsComponent/accountSettingsInputPassword.vue";
import DashboardSettingsInputWireguardConfigurationPath
from "@/components/settingsComponent/dashboardSettingsInputWireguardConfigurationPath.vue";
import DashboardTheme from "@/components/settingsComponent/dashboardTheme.vue";
import DashboardSettingsInputIPAddressAndPort
from "@/components/settingsComponent/dashboardSettingsInputIPAddressAndPort.vue";
import DashboardAPIKeys from "@/components/settingsComponent/dashboardAPIKeys.vue";
import AccountSettingsMFA from "@/components/settingsComponent/accountSettingsMFA.vue";
export default {
name: "settings",
methods: {ipV46RegexCheck},
components: {
AccountSettingsMFA,
DashboardAPIKeys,
DashboardSettingsInputIPAddressAndPort,
DashboardTheme,
DashboardSettingsInputWireguardConfigurationPath,
AccountSettingsInputPassword, AccountSettingsInputUsername, PeersDefaultSettingsInput},
setup(){
const dashboardConfigurationStore = DashboardConfigurationStore()
return {dashboardConfigurationStore}
},
watch: {
// 'dashboardConfigurationStore.Configuration': {
// deep: true,
// handler(){
// this.dashboardConfigurationStore.updateConfiguration();
// }
// }
}
}
</script>
<template>
<div class="mt-md-5 mt-3">
<div class="container-md">
<h3 class="mb-3 text-body">Settings</h3>
<DashboardTheme></DashboardTheme>
<div class="card mb-4 shadow rounded-3">
<p class="card-header">Peers Default Settings</p>
<div class="card-body">
<PeersDefaultSettingsInput targetData="peer_global_dns" title="DNS"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="peer_endpoint_allowed_ip" title="Peer Endpoint Allowed IPs"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="peer_mtu" title="MTU (Max Transmission Unit)"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="peer_keep_alive" title="Persistent Keepalive"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="remote_endpoint" title="Peer Remote Endpoint"
:warning="true" warningText="This will be changed globally, and will be apply to all peer's QR code and configuration file."
></PeersDefaultSettingsInput>
</div>
</div>
<div class="card mb-4 shadow rounded-3">
<p class="card-header">WireGuard Configurations Settings</p>
<div class="card-body">
<DashboardSettingsInputWireguardConfigurationPath
targetData="wg_conf_path"
title="Configurations Directory"
:warning="true"
warning-text="Remember to remove <code>/</code> at the end of your path. e.g <code>/etc/wireguard</code>"
>
</DashboardSettingsInputWireguardConfigurationPath>
</div>
</div>
<div class="card mb-4 shadow rounded-3">
<p class="card-header">Account Settings</p>
<div class="card-body d-flex gap-4 flex-column">
<AccountSettingsInputUsername targetData="username"
title="Username"
></AccountSettingsInputUsername>
<hr class="m-0">
<AccountSettingsInputPassword
targetData="password">
</AccountSettingsInputPassword>
<hr class="m-0" v-if="!this.dashboardConfigurationStore.getActiveCrossServer()">
<AccountSettingsMFA v-if="!this.dashboardConfigurationStore.getActiveCrossServer()"></AccountSettingsMFA>
</div>
</div>
<DashboardAPIKeys></DashboardAPIKeys>
</div>
</div>
</template>
<style scoped>
</style>

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