This commit is contained in:
Noxcis 2023-12-09 19:38:58 -08:00
parent bc1f6edc5a
commit 65da97a367
69 changed files with 8341 additions and 21 deletions

View File

@ -1,7 +1,7 @@
#FROM nginx:alpine3.18
FROM node:20.9.0-alpine3.18
RUN apk update && apk add --no-cache bash
RUN apk update && apk add --no-cache bash nginx
USER node:node

View File

@ -0,0 +1,12 @@
[Interface]
PrivateKey = 4KX5qp4T8ANIoEk/eFJuYwi2g7/qD/6U9bzg2fUb60U=
Address = 10.0.0.254/32
DNS = 10.2.0.100,10.2.0.100
MTU = 1420
[Peer]
PublicKey = STk0MVroIsxKg0yMkorsQTiTFhk1R99yjP97Soe4MVA=
AllowedIPs = 0.0.0.0/0
Endpoint = 0.0.0.0:443
PersistentKeepalive = 21
PresharedKey = nD4vaypUdgiHFfxDU5Fb6pWuN3KDVgKMsfIpndaYHj8=

19
WG-Dash/.dockerignore Normal file
View File

@ -0,0 +1,19 @@
.vscode
.DS_Store
.idea
src/db
__pycache__
src/test.py
*.db
master-key/master.conf
env/
src/wg-dashboard.ini
src/static/pic.xd
*.conf
private_key.txt
public_key.txt
venv/**
log/**
release/*
src/db/wgdashboard.db
.jshintrc

21
WG-Dash/Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM alpine:latest
WORKDIR /home/app
RUN apk update && \
apk add --no-cache python3 py3-pip py3-bcrypt py3-pillow uwsgi-python3 && \
apk add --no-cache build-base linux-headers wireguard-tools openssl nginx && \
apk add --no-cache net-tools iproute2 iptables ip6tables openssl-dev && \
apk add --no-cache inotify-tools procps openresolv libc-dev pcre-dev && \
mkdir /home/app/master-key
COPY ./src /home/app
RUN pip install --upgrade pip --no-cache-dir --break-system-packages && \
python3 -m pip install -r /home/app/requirements.txt --no-cache-dir --break-system-packages && \
chmod u+x /home/app/entrypoint.sh
ENTRYPOINT ["/home/app/entrypoint.sh"]

201
WG-Dash/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

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

@ -0,0 +1,13 @@
#!/bin/bash
WIREGUARD_INTERFACE=GUESTS
WIREGUARD_LAN=192.168.20.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,30 @@
#!/bin/bash
WIREGUARD_INTERFACE=GUESTS
WIREGUARD_LAN=192.168.20.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
# Drop incoming traffic from guests to wg-dashboard
iptables -A INPUT -i $WIREGUARD_INTERFACE -j DROP
# DNS
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.100 -p udp --dport 53 -j ACCEPT
# Drop traffic to your any private IP address
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 -j DROP
# Accept outgoing connections to HTTP(S) ports to any IP address (public because of rule above)
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 0.0.0.0/0 -p tcp -m multiport --dports 80,443 -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

@ -0,0 +1,20 @@
#!/bin/bash
WIREGUARD_INTERFACE=IPV6ADMINS
WIREGUARD_LAN=192.168.30.1/24
WIREGUARD_LAN_IPV6=2001:db8:30:1::/64
MASQUERADE_INTERFACE=eth0
CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
iptables -t nat -D POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
ip6tables -t nat -D POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN_IPV6
# Remove and delete the WIREGUARD_wg0 chain
iptables -D FORWARD -j $CHAIN_NAME
iptables -F $CHAIN_NAME
iptables -X $CHAIN_NAME
# Remove and delete the WIREGUARD_wg0 chain
ip6tables -D FORWARD -j $CHAIN_NAME
ip6tables -F $CHAIN_NAME
ip6tables -X $CHAIN_NAME

View File

@ -0,0 +1,37 @@
#!/bin/bash
WIREGUARD_INTERFACE=IPV6ADMINS
WIREGUARD_LAN=192.168.30.1/24
WIREGUARD_LAN_IPV6=2001:db8:30:1::/64
MASQUERADE_INTERFACE=eth0
iptables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
ip6tables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN_IPV6
# Add a WIREGUARD_wg0 chain to the FORWARD chain
CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
iptables -N $CHAIN_NAME
iptables -A FORWARD -j $CHAIN_NAME
ip6tables -N $CHAIN_NAME
ip6tables -A FORWARD -j $CHAIN_NAME
# Accept related or established traffic
iptables -A $CHAIN_NAME -o $WIREGUARD_INTERFACE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
ip6tables -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
ip6tables -A $CHAIN_NAME -s $WIREGUARD_LAN_IPV6 -i $WIREGUARD_INTERFACE -j ACCEPT
# Allow traffic to the local loopback interface
iptables -A $CHAIN_NAME -o lo -j ACCEPT
# Allow traffic to the local loopback interface
ip6tables -A $CHAIN_NAME -o lo -j ACCEPT
# Drop everything else coming through the Wireguard interface
iptables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP
ip6tables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP
# Return to FORWARD chain
iptables -A $CHAIN_NAME -j RETURN
ip6tables -A $CHAIN_NAME -j RETURN

View File

@ -0,0 +1,20 @@
#!/bin/bash
WIREGUARD_INTERFACE=IPV6MEMBERS
WIREGUARD_LAN=192.168.40.1/24
WIREGUARD_LAN_IPV6=2001:db8:40:1::/64
MASQUERADE_INTERFACE=eth0
CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
iptables -t nat -D POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
ip6tables -t nat -D POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN_IPV6
# Remove and delete the WIREGUARD_wg0 chain
iptables -D FORWARD -j $CHAIN_NAME
iptables -F $CHAIN_NAME
iptables -X $CHAIN_NAME
# Remove and delete the WIREGUARD_wg0 chain
ip6tables -D FORWARD -j $CHAIN_NAME
ip6tables -F $CHAIN_NAME
ip6tables -X $CHAIN_NAME

View File

@ -0,0 +1,50 @@
#!/bin/bash
WIREGUARD_INTERFACE=IPV6MEMBERS
WIREGUARD_LAN=192.168.40.1/24
WIREGUARD_LAN_IPV6=2001:db8:40:1::/64
MASQUERADE_INTERFACE=eth0
iptables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
ip6tables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN_IPV6
# Add a WIREGUARD_wg0 chain to the FORWARD chain
CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
iptables -N $CHAIN_NAME
iptables -A FORWARD -j $CHAIN_NAME
ip6tables -N $CHAIN_NAME
ip6tables -A FORWARD -j $CHAIN_NAME
# Accept related or established traffic
iptables -A $CHAIN_NAME -o $WIREGUARD_INTERFACE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
ip6tables -A $CHAIN_NAME -o $WIREGUARD_INTERFACE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Drop incoming traffic from wg1 to wg-dashboard
iptables -A INPUT -i $WIREGUARD_INTERFACE -j DROP
ip6tables -A INPUT -i $WIREGUARD_INTERFACE -j DROP
# Accept DNS from Adguard
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.100 -p udp --dport 53 -j ACCEPT
# Accept Channels FEC
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.4 -p tcp --dport 80 -j ACCEPT
# Drop Direct Forward traffic to Dockge
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.2 -j DROP
# Drop Forward traffic to AdGuard Dashboard
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.100 -j DROP
# Drop Forward traffic to Unbound (members should be restricted form accesing the means of modifying the network)
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.200 -j DROP
# Drop Forward traffic to Channels Database
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.5 -j DROP
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.4 -j DROP
# Accept outgoing connections to HTTP(S) ports to any IP address (public because of rule above)
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 0.0.0.0/0 -p tcp -m multiport --dports 20,21,22,80,443,3389 -j ACCEPT
# Accept outgoing connections to HTTP(S) ports to any IP address (public because of rule above)
ip6tables -A $CHAIN_NAME -s $WIREGUARD_LAN_IPV6 -i $WIREGUARD_INTERFACE -d ::/0 -p tcp -m multiport --dports 20,21,22,80,443,3389 -j ACCEPT
# Drop everything else coming through the Wireguard interface
iptables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP
ip6tables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP
# Return to FORWARD chain
iptables -A $CHAIN_NAME -j RETURN
ip6tables -A $CHAIN_NAME -j RETURN

View File

@ -0,0 +1,13 @@
#!/bin/bash
WIREGUARD_INTERFACE=LANP2P
WIREGUARD_LAN=172.16.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,23 @@
#!/bin/bash
WIREGUARD_INTERFACE=LANP2P
WIREGUARD_LAN=172.16.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 on connections to peers of LAN zone only
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 172.16.0.1/24 -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

@ -0,0 +1,13 @@
#!/bin/bash
WIREGUARD_INTERFACE=MEMEBERS
WIREGUARD_LAN=192.168.10.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,43 @@
#!/bin/bash
WIREGUARD_INTERFACE=MEMEBERS
WIREGUARD_LAN=192.168.10.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
# Drop incoming traffic from wg1 to wg-dashboard
iptables -A INPUT -i $WIREGUARD_INTERFACE -j DROP
# Accept DNS from Adguard
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.100 -p udp --dport 53 -j ACCEPT
# Accept Channels FEC
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.4 -p tcp --dport 80 -j ACCEPT
# Drop Direct Forward traffic to Dockge
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.2 -j DROP
# Drop Direct Forward traffic to AdGuard Dashboard
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.100 -j DROP
# Drop Direct Forward traffic to Unbound
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.200 -j DROP
# Drop Forward traffic to Channels Database
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.5 -j DROP
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 10.2.0.4 -j DROP
# Accept outgoing connections to HTTP(S) ports to any IP address (public because of rule above)
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -d 0.0.0.0/0 -p tcp -m multiport --dports 20,21,22,80,443,3389 -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

55
WG-Dash/src/api.py Normal file
View File

@ -0,0 +1,55 @@
import ipaddress, subprocess, datetime, os, util
from util import *
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 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()

1818
WG-Dash/src/dashboard.py Normal file

File diff suppressed because it is too large Load Diff

99
WG-Dash/src/entrypoint.sh Normal file
View File

@ -0,0 +1,99 @@
#!/bin/bash
chmod u+x /home/app/wgd.sh
chmod u+x /home/app/FIREWALLS/Admins/wg0-dwn.sh
chmod u+x /home/app/FIREWALLS/Admins/wg0-nat.sh
chmod u+x /home/app/FIREWALLS/Members/wg1-dwn.sh
chmod u+x /home/app/FIREWALLS/Members/wg1-nat.sh
chmod u+x /home/app/FIREWALLS/LAN-only-users/wg2-dwn.sh
chmod u+x /home/app/FIREWALLS/LAN-only-users/wg2-nat.sh
chmod u+x /home/app/FIREWALLS/Guest/wg3-dwn.sh
chmod u+x /home/app/FIREWALLS/Guest/wg3-nat.sh
chmod u+x /home/app/FIREWALLS/IPV6/wg0-dwn.sh
chmod u+x /home/app/FIREWALLS/IPV6/wg0-nat.sh
chmod u+x /home/app/FIREWALLS/IPV6/wg1-dwn.sh
chmod u+x /home/app/FIREWALLS/IPV6/wg1-nat.sh
if [ ! -f "/etc/wireguard/wg0.conf" ]; then
/home/app/wgd.sh newconfig
fi
run_wireguard_up() {
config_files=$(find /etc/wireguard -type f -name "*.conf")
for file in $config_files; do
config_name=$(basename "$file" ".conf")
chmod 600 "/etc/wireguard/$config_name.conf"
done
}
config_nginx () {
rm /etc/nginx/http.d/default.conf
cat <<EOF > "/etc/nginx/http.d/default.conf"
server {
listen 80;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:10086; # uWSGI service address and port
}
# Set the location of the uWSGI static folder
location /static/ {
alias /home/app/static/;
}
# Set security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
# Disable server version information
server_tokens off;
# Configure error pages
error_page 400 401 402 403 404 /error.html;
location = /error.html {
root /path/to/error/pages;
}
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
EOF
}
run_wireguard_up #>/dev/null 2>&1 &&
wg-quick up ADMINS
config_nginx &&
nginx &&
/home/app/wgd.sh start

View File

@ -0,0 +1,5 @@
Flask
ifcfg
configparser
icmplib
flask-qrcode

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,934 @@
body {
font-size: .875rem;
/*font-family: 'Poppins', sans-serif;*/
}
.codeFont{
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
.btn-primary {
font-weight: bold;
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100;
/* Behind the navbar */
padding: 48px 0 0;
/* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto;
/* Scrollable contents if viewport is shorter than content. */
}
@supports ((position: -webkit-sticky) or (position: sticky)) {
.sidebar-sticky {
position: -webkit-sticky;
position: sticky;
}
}
.sidebar .nav-link, .bottomNavContainer .nav-link{
font-weight: 500;
color: #333;
transition: 0.2s cubic-bezier(0.82, -0.07, 0, 1.01);
}
.nav-link:hover {
padding-left: 30px;
background-color: #dfdfdf;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #999;
}
.sidebar .nav-link.active, .bottomNavContainer .nav-link.active {
color: #007bff;
}
.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
color: inherit;
}
.sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
}
/*
* Navbar
*/
.navbar-brand {
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
}
.navbar .navbar-toggler {
top: .25rem;
right: 1rem;
}
.form-control {
transition: all 0.2s ease-in-out;
}
.form-control:disabled {
cursor: not-allowed;
}
.navbar .form-control {
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}
.dot {
width: 10px;
height: 10px;
border-radius: 50px;
display: inline-block;
margin-left: auto !important;
}
.dot-running {
background-color: #28a745!important;
box-shadow: 0 0 0 0.2rem #28a74545;
}
.h6-dot-running {
margin-left: 0.3rem;
}
.dot-stopped {
background-color: #6c757d!important;
}
.card-running {
border-color: #28a745;
}
.info h6 {
line-break: anywhere;
transition: all 0.4s cubic-bezier(0.96, -0.07, 0.34, 1.01);
opacity: 1;
}
.info .row .col-sm {
display: flex;
flex-direction: column;
}
.info .row .col-sm small {
display: flex;
}
.info .row .col-sm small strong:last-child(1) {
margin-left: auto !important;
}
.btn-control {
border: none !important;
padding: 0;
margin: 0 1rem 0 0;
}
.btn-control:hover{
background-color: transparent !important;
}
.btn-control:active,
.btn-control:focus {
background-color: transparent !important;
border: none !important;
box-shadow: none;
}
.btn-qrcode-peer {
padding: 0 !important;
}
.btn-qrcode-peer:active,
.btn-qrcode-peer:hover {
transform: scale(0.9) rotate(180deg);
border: 0 !important;
}
.btn-download-peer:active,
.btn-download-peer:hover {
color: #17a2b8 !important;
transform: translateY(5px);
}
.share_peer_btn_group .btn-control {
margin: 0 0 0 1rem;
padding: 0 !important;
transition: all 0.4s cubic-bezier(1, -0.43, 0, 1.37);
}
.btn-control:hover {
background: white;
}
.btn-delete-peer:hover {
color: #dc3545;
}
.btn-lock-peer:hover {
color: #28a745;
}
.btn-lock-peer.lock{
color: #6c757d
}
.btn-lock-peer.lock:hover{
color: #6c757d
}
.btn-control.btn-outline-primary:hover{
color: #007bff
}
/* .btn-setting-peer:hover {
color: #007bff
} */
.btn-download-peer:hover {
color: #17a2b8;
}
.login-container {
padding: 2rem;
}
@media (max-width: 992px) {
.card-col {
margin-bottom: 1rem;
}
}
.switch {
font-size: 2rem;
}
.switch:hover {
text-decoration: none
}
.btn-group-label:hover {
color: #007bff;
border-color: #007bff;
background: white;
}
.peer_data_group {
text-align: right;
display: flex;
margin-bottom: 0.5rem
}
.peer_data_group p {
text-transform: uppercase;
margin-bottom: 0;
margin-right: 1rem
}
@media (max-width: 768px) {
.peer_data_group {
text-align: left;
}
}
.index-switch {
display: flex;
align-items: center;
justify-content: flex-end;
}
main {
margin-bottom: 3rem;
}
.peer_list {
margin-bottom: 7rem
}
@media (max-width: 768px) {
.add_btn {
bottom: 1.5rem !important;
}
.peer_list {
margin-bottom: 7rem !important;
}
}
.btn-manage-group {
z-index: 99;
position: fixed;
bottom: 3rem;
right: 2rem;
display: flex;
}
.btn-manage-group .setting_btn_menu {
position: absolute;
top: -124px;
background-color: white;
padding: 1rem 0;
right: 0;
box-shadow: 0 10px 20px rgb(0 0 0 / 19%), 0 6px 6px rgb(0 0 0 / 23%);
border-radius: 10px;
min-width: 250px;
display: none;
transform: translateY(-30px);
opacity: 0;
transition: all 0.3s cubic-bezier(0.58, 0.03, 0.05, 1.28);
}
.btn-manage-group .setting_btn_menu.show {
display: block;
}
.setting_btn_menu.showing {
transform: translateY(0px);
opacity: 1;
}
.setting_btn_menu a {
display: flex;
padding: 0.5rem 1rem;
transition: all 0.1s ease-in-out;
font-size: 1rem;
align-items: center;
cursor: pointer;
}
.setting_btn_menu a:hover {
background-color: #efefef;
text-decoration: none;
}
.setting_btn_menu a i {
margin-right: auto !important;
}
.add_btn {
height: 54px;
z-index: 99;
border-radius: 100px !important;
padding: 0 14px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
margin-right: 1rem;
font-size: 1.5rem;
}
.setting_btn {
height: 54px;
z-index: 99;
border-radius: 100px !important;
padding: 0 14px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
font-size: 1.5rem;
}
@-webkit-keyframes rotating
/* Safari and Chrome */
{
from {
-webkit-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes rotating {
from {
-ms-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-ms-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-webkit-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.rotating::before {
-webkit-animation: rotating 0.75s linear infinite;
-moz-animation: rotating 0.75s linear infinite;
-ms-animation: rotating 0.75s linear infinite;
-o-animation: rotating 0.75s linear infinite;
animation: rotating 0.75s linear infinite;
}
.peer_private_key_textbox_switch {
position: absolute;
right: 2rem;
transform: translateY(-28px);
font-size: 1.2rem;
cursor: pointer;
}
#peer_private_key_textbox,
#private_key,
#public_key,
#peer_preshared_key_textbox {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
.progress-bar {
transition: 0.3s ease-in-out;
}
.key {
transition: 0.2s ease-in-out;
cursor: pointer;
}
.key:hover {
color: #007bff;
}
.card {
border-radius: 10px;
}
.peer_list .card .button-group {
height: 22px;
}
.form-control {
border-radius: 10px;
}
.btn {
border-radius: 8px;
/*padding: 0.6rem 0.9em;*/
}
.login-box #username,
.login-box #password {
padding: 0.6rem calc( 0.9rem + 32px);
height: inherit;
}
.login-box label[for="username"],
.login-box label[for="password"] {
font-size: 1rem;
margin: 0 !important;
transform: translateY(30px) translateX(16px);
padding: 0;
}
/*label[for="password"]{*/
/* transform: translateY(32px) translateX(16px);*/
/*}*/
.modal-content {
border-radius: 10px;
}
.tooltip-inner {
font-size: 0.8rem;
}
@-webkit-keyframes loading {
0% {
background-color: #dfdfdf;
}
50% {
background-color: #adadad;
}
100% {
background-color: #dfdfdf;
}
}
@-moz-keyframes loading {
0% {
background-color: #dfdfdf;
}
50% {
background-color: #adadad;
}
100% {
background-color: #dfdfdf;
}
}
.conf_card {
transition: 0.2s ease-in-out;
}
.conf_card:hover {
border-color: #007bff;
cursor: pointer;
}
.info_loading {
/* animation: loading 2s infinite ease-in-out;
/* border-radius: 5px; */
height: 19.19px;
/* transition: 0.3s ease-in-out; */
/* transform: translateX(40px); */
opacity: 0 !important;
}
#conf_status_btn {
transition: 0.2s ease-in-out;
}
#conf_status_btn.info_loading {
height: 38px;
border-radius: 5px;
animation: loading 3s infinite ease-in-out;
}
#qrcode_img img {
width: 100%;
}
#selected_ip_list .badge,
#selected_peer_list .badge {
margin: 0.1rem
}
#add_modal.ip_modal_open {
transition: filter 0.2s ease-in-out;
filter: brightness(0.5);
}
#delete_bulk_modal .list-group a.active {
background-color: #dc3545;
border-color: #dc3545;
}
#selected_peer_list {
max-height: 80px;
overflow-y: scroll;
overflow-x: hidden;
}
.no-response {
width: 100%;
height: 100%;
position: fixed;
background: #000000ba;
z-index: 10000;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
opacity: 0;
transition: all 1s ease-in-out;
}
.no-response.active {
display: flex;
}
.no-response.active.show {
opacity: 100;
}
.no-response .container>* {
text-align: center;
}
.no-responding {
transition: all 1s ease-in-out;
filter: blur(10px);
}
pre.index-alert {
margin-bottom: 0;
padding: 1rem;
background-color: #343a40;
border: 1px solid rgba(0, 0, 0, .125);
border-radius: .25rem;
margin-top: 1rem;
color: white;
}
.peerNameCol {
display: flex;
align-items: center;
margin-bottom: 0.2rem
}
.peerName {
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.peerLightContainer {
text-transform: uppercase;
margin: 0;
margin-left: auto !important;
}
.conf_card .dot,
.info .dot {
transform: translateX(10px);
}
#config_body {
transition: 0.3s ease-in-out;
}
#config_body.firstLoading {
opacity: 0.2;
}
.chartTitle {
display: flex;
}
.chartControl {
margin-bottom: 1rem;
display: flex;
align-items: center;
}
.chartTitle h6 {
margin-bottom: 0;
line-height: 1;
margin-right: 0.5rem;
}
.chartContainer.fullScreen {
position: fixed;
z-index: 9999;
background-color: white;
top: 0;
left: 0;
width: calc( 100% + 15px);
height: 100%;
padding: 32px;
}
.chartContainer.fullScreen .col-sm {
padding-right: 0;
height: 100%;
}
.chartContainer.fullScreen .chartCanvasContainer {
width: 100%;
height: calc( 100% - 47px) !important;
max-height: calc( 100% - 47px) !important;
}
#switch{
transition: all 200ms ease-in;
}
.toggle--switch{
display: none;
}
.toggleLabel{
width: 64px;
height: 32px;
background-color: #6c757d17;
display: flex;
position: relative;
border: 2px solid #6c757d8c;
border-radius: 100px;
transition: all 200ms ease-in;
cursor: pointer;
margin: 0;
}
.toggle--switch.waiting + .toggleLabel{
opacity: 0.5;
}
.toggleLabel::before{
background-color: #6c757d;
height: 26px;
width: 26px;
content: "";
border-radius: 100px;
margin: 1px;
position: absolute;
animation-name: off;
animation-duration: 350ms;
animation-fill-mode: forwards;
transition: all 200ms ease-in;
cursor: pointer;
}
.toggleLabel:hover::before{
filter: brightness(1.2);
}
.toggle--switch:checked + .toggleLabel{
background-color: #007bff17 !important;
border: 2px solid #007bff8c;
}
.toggle--switch:checked + .toggleLabel::before{
background-color: #007bff;
animation-name: on;
animation-duration: 350ms;
animation-fill-mode: forwards;
}
@keyframes on {
0%{
left: 0px;
}
60%{
left: 0px;
width: 40px;
}
100%{
left: 32px;
width: 26px;
}
}
@keyframes off {
0%{
left: 32px;
}
60%{
left: 18px;
width: 40px;
}
100%{
left: 0px;
width: 26px;
}
}
.toastContainer{
z-index: 99999 !important;
}
.toast{
min-width: 300px;
background-color: rgba(255,255,255,1);
z-index: 99999;
}
.toast-header{
background-color: rgba(255,255,255);
}
.toast-progressbar{
width: 100%;
height: 4px;
background-color: #007bff;
border-bottom-left-radius: .25rem;
}
.addConfigurationAvailableIPs{
margin-bottom: 0;
}
.input-feedback{
display: none;
}
#addConfigurationModal label{
display: flex;
width: 100%;
align-items: center;
}
#addConfigurationModal label a{
margin-left: auto !important;
}
#reGeneratePrivateKey{
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
}
.addConfigurationToggleStatus.waiting{
opacity: 0.5;
}
/*.conf_card .card-body .row .card-col{*/
/* margin-bottom: 0.5rem;*/
/*}*/
.peerDataUsageChartContainer{
min-height: 50vh;
width: 100%;
}
.peerDataUsageChartControl{
display: block !important;
margin: 0;
}
.peerDataUsageChartControl .switchUnit{
width: 33.3%;
}
.peerDataUsageChartControl .switchTimePeriod{
width: 25%;
}
@media (min-width: 1200px){
#peerDataUsage .modal-xl {
max-width: 95vw;
}
}
.bottom{
display: none;
}
@media (max-width: 768px){
.bottom{
display: block;
}
.btn-manage-group{
bottom: calc( 3rem + 40px + env(safe-area-inset-bottom, 5px));
}
main{
padding-bottom: calc( 3rem + 40px + env(safe-area-inset-bottom, 5px));
}
}
.bottomNavContainer{
display: flex;
color: #333;
padding-bottom: env(safe-area-inset-bottom, 5px);
box-shadow: inset 0 1px 0 rgb(0 0 0 / 10%);
}
.bottomNavButton{
width: 25vw;
display: flex;
flex-direction: column;
align-items: center;
margin: 0.7rem 0;
color: rgba(51, 51, 51, 0.5);
cursor: pointer;
transition: all ease-in 0.2s;
}
.bottomNavButton.active{
color: #333;
}
.bottomNavButton i{
font-size: 1.2rem;
}
.bottomNavButton .subNav{
width: 100vw;
position: absolute;
z-index: 10000;
bottom: 0;
left: 0;
background-color: #272b30;
display: none;
animation-duration: 400ms;
padding-bottom: env(safe-area-inset-bottom, 5px);
}
.bottomNavButton .subNav.active{
display: block;
}
.bottomNavButton .subNav .nav .nav-item .nav-link{
padding: 0.7rem 1rem;
}
.bottomNavWrapper{
height: 100%;
width: 100%;
background-color: #000000a1;
position: fixed;
z-index: 1030;
display: none;
left: 0;
}
.bottomNavWrapper.active{
display: block;
}
.sb-update-url .dot-running{
transform: translateX(10px);
}
.list-group-item{
transition: all 0.1s ease-in;
}
.theme-switch-btn{
width: 100%;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,391 @@
:root {
--green: hsl(120deg, 30%, 50%) !important;
--blue: hsl(235deg, 60%, 60%) !important;
--red: hsl(0deg, 60%, 60%) !important;
--magenta: hsl(315deg, 60%, 60%) !important;
}
body {
background: #222222;
color: hsl(0deg, 0%, 80%);
}
a.text-primary:focus, a.text-primary:hover {
color: hsl(235deg, 60%, 50%) !important;
}
.btn-primary {
color: hsl(0deg, 0%, 90%) !important;
border-color: hsl(235deg, 60%, 60%) !important;
background: hsl(235deg, 60%, 60%) !important;
}
.btn-primary:hover {
background-color: hsl(235deg, 60%, 50%) !important;
border-color: hsl(235deg, 60%, 50%) !important;
}
.btn-outline-primary {
color: hsl(235deg, 60%, 60%) !important;
border-color: hsl(235deg, 60%, 60%) !important;
background: transparent !important;
}
.btn-outline-primary:hover, .btn-outline-primary.active {
color: hsl(0deg, 0%, 90%) !important;
}
.btn-outline-primary.active {
background-color: hsl(235deg, 60%, 60%) !important;
border-color: hsl(235deg, 60%, 60%) !important;
}
.btn-outline-primary:hover {
background-color: hsl(235deg, 60%, 50%) !important;
border-color: hsl(235deg, 60%, 50%) !important;
}
.btn-success {
color: hsl(0deg, 0%, 90%) !important;
border-color: hsl(120deg, 30%, 50%) !important;
background: hsl(120deg, 30%, 50%) !important;
}
.btn-success:hover {
background-color: hsl(120deg, 30%, 40%) !important;
border-color: hsl(120deg, 30%, 40%) !important;
}
.list-group-item {
background-color: #272727;
border-color: #272727;
color: white;
}
.list-group-item:hover {
background-color: #333333;
border-color: #333333;
color: white;
}
.delete-peer-bulk-badge.badge-danger {
background-color: hsl(0deg, 60%, 60%);
}
.delete-peer-bulk-badge.badge-danger:hover {
background-color: hsl(0deg, 60%, 50%);
}
#delete_bulk_modal .list-group a.active {
background-color: hsl(0deg, 60%, 60%);
border: hsl(0deg, 60%, 60%);
}
#delete_bulk_modal .list-group a.active:hover {
background-color: hsl(0deg, 60%, 50%);
border: hsl(0deg, 60%, 50%);
}
#available_ip_modal .list-group a.active {
background-color: hsl(235deg, 60%, 60%);
border: hsl(235deg, 60%, 60%);
}
#available_ip_modal .list-group a.active:hover {
background-color: hsl(235deg, 60%, 50%);
border: hsl(235deg, 60%, 50%);
}
.available-ip-badge.badge-primary {
background-color: hsl(235deg, 60%, 60%);
}
.available-ip-badge.badge-primary:hover {
background-color: hsl(235deg, 60%, 50%);
}
.btn-outline-success {
color: hsl(120deg, 30%, 50%) !important;
border-color: hsl(120deg, 30%, 50%) !important;
background: transparent !important;
}
.btn-outline-success:hover, .btn-outline-success.active {
color: hsl(0deg, 0%, 90%) !important;
}
.btn-outline-success.active {
background-color: hsl(120deg, 30%, 50%) !important;
border-color: hsl(120deg, 30%, 50%) !important;
}
.btn-outline-success:hover {
background-color: hsl(120deg, 30%, 40%) !important;
border-color: hsl(120deg, 30%, 40%) !important;
}
.btn-danger {
color: hsl(0deg, 0%, 90%) !important;
border-color: hsl(0deg, 60%, 60%) !important;
background: hsl(0deg, 60%, 60%) !important;
}
.btn-danger:hover {
background-color: hsl(0deg, 60%, 50%) !important;
border-color: hsl(0deg, 60%, 50%) !important;
}
.btn-outline-danger {
color: hsl(0deg, 60%, 60%) !important;
border-color: hsl(0deg, 60%, 60%) !important;
background: transparent !important;
}
.btn-outline-danger:hover, .btn-outline-danger.active {
color: hsl(0deg, 0%, 90%) !important;
}
.btn-outline-danger.active {
background-color: hsl(0deg, 60%, 60%) !important;
border-color: hsl(0deg, 60%, 60%) !important;
}
.btn-outline-danger:hover {
background-color: hsl(0deg, 60%, 50%) !important;
border-color: hsl(0deg, 60%, 50%) !important;
}
.btn-secondary {
color: hsl(0deg, 0%, 90%) !important;
border-color: #424242 !important;
background: #424242 !important;
}
.btn-secondary:hover {
background-color: #383838 !important;
border-color: #383838 !important;
}
.btn-outline-secondary {
color: #424242 !important;
border-color: #424242 !important;
background: transparent !important;
}
.btn-outline-secondary:hover, .btn-outline-secondary.active {
color: hsl(0deg, 0%, 90%) !important;
}
.btn-outline-secondary.active {
background-color: #424242 !important;
border-color: #424242 !important;
}
.btn-outline-secondary:hover {
background-color: #383838 !important;
border-color: #383838 !important;
}
.btn-control.btn-lock-peer.lock {
color: hsl(0deg, 60%, 60%) !important;
}
.btn-control.btn-lock-peer.lock:hover {
color: hsl(0deg, 60%, 50%) !important;
}
.btn-control:hover {
background-color: transparent !important;
}
.btn-control:hover.btn-outline-primary {
color: hsl(235deg, 60%, 50%) !important;
}
.btn-control:hover.btn-outline-success {
color: hsl(120deg, 30%, 40%) !important;
}
.btn-control:hover.btn-outline-danger {
color: hsl(0deg, 60%, 50%) !important;
}
.btn-control:hover.btn-outline-secondary {
color: #383838 !important;
}
.form-control {
background-color: #2c2c2c !important;
border-color: transparent !important;
color: hsl(0deg, 0%, 80%) !important;
}
.form-control:disabled {
color: #777777 !important;
}
.card .form-control {
background: #2c2c2c !important;
}
.conf_card a {
color: hsl(235deg, 60%, 60%);
}
.conf_card:hover {
border-color: hsl(235deg, 60%, 60%);
}
.sidebar .nav-link,
.bottomNavContainer .nav-link {
color: hsl(0deg, 0%, 80%);
}
.sidebar .nav-link:hover,
.bottomNavContainer .nav-link:hover {
background: #222222;
}
nav#sidebarMenu.col-md-3.col-lg-2.d-md-block.bg-light.sidebar.collapse,
.navbar-brand,
.bg-dark {
background-color: #1e1e1e !important;
background: #1e1e1e !important;
}
.card {
background: #272727;
}
.text-muted {
color: hsl(0deg, 0%, 50%) !important;
}
.text-danger {
color: hsl(0deg, 60%, 60%) !important;
}
.text-success {
color: hsl(120deg, 30%, 50%) !important;
}
.text-primary {
color: hsl(235deg, 60%, 60%) !important;
}
.text-info {
color: hsl(190deg, 60%, 60%) !important;
}
a.text-success:focus,
a.text-success:hover {
color: hsl(120deg, 30%, 40%) !important;
}
a.text-danger:focus,
a.text-danger:hover {
color: hsl(0deg, 60%, 50%) !important;
}
a.text-info:focus,
a.text-info:hover {
color: hsl(190deg, 60%, 50%) !important;
}
.dot-running {
background-color: hsl(120deg, 30%, 50%) !important;
}
.card-running {
border-color: hsl(120deg, 30%, 50%);
}
.toggle--switch:checked + .toggleLabel::before {
background-color: hsl(235deg, 60%, 60%) !important;
}
.toggle--switch:checked + .toggleLabel {
background-color: #2e336b !important;
border-color: hsl(235deg, 60%, 60%) !important;
}
.sidebar .nav-link.active,
.bottomNavContainer .nav-link.active {
color: hsl(235deg, 60%, 60%) !important;
}
hr {
border-color: #2e2e2e;
}
.modal-content {
background-color: #222222;
}
.modal-header,
.modal-footer {
background-color: #1e1e1e;
border-color: #2e2e2e;
}
code {
color: hsl(315deg, 60%, 60%);
}
.close {
color: hsl(0deg, 0%, 80%);
text-shadow: none;
}
.close:hover {
color: hsl(0deg, 0%, 70%);
}
.chartContainer.fullScreen {
background-color: #222222 !important;
}
.popover {
background-color: #333333 !important;
border: none !important;
}
.popover-body {
color: hsl(0deg, 0%, 80%) !important;
}
div.toast {
background-color: #424242 !important;
}
div.toast div.toast-header {
background-color: #333333 !important;
color: hsl(0deg, 0%, 80%) !important;
border-bottom-color: #424242 !important;
}
div.toast div.toast-body {
background-color: #383838 !important;
color: hsl(0deg, 0%, 80%) !important;
}
div.toast div.toast-body.text-danger {
color: hsl(0deg, 60%, 60%) !important;
}
div.toast div.toast-progressbar {
background-color: hsl(235deg, 60%, 60%) !important;
}
div.toast div.toast-progressbar.bg-danger {
background-color: hsl(0deg, 60%, 60%) !important;
}
.bs-popover-auto[x-placement^=right] > .arrow::after,
.bs-popover-right > .arrow::after {
border-right-color: #333333 !important;
}
.btn-manage-group .setting_btn_menu {
background-color: #2c2c2c !important;
}
.setting_btn_menu a:hover {
background-color: #333333 !important;
}
.table {
color: hsl(0deg, 0%, 80%) !important;
}
.table th,
.table td {
border-color: #333333 !important;
}
.btn-outline-primary.focus, .btn-outline-primary:focus, .btn-primary.focus, .btn-primary:focus {
box-shadow: 0 0 0 0.2rem rgba(144, 153, 255, 0.29) !important;
}
.bottomNav {
background-color: #272727 !important;
}
.bottomNav .bottomNavButton {
color: hsl(0deg, 0%, 60%);
}
.bottomNav .bottomNavButton.active {
color: hsl(235deg, 60%, 60%) !important;
}
.bottomNav .subNav {
background-color: #272727 !important;
}
.key:hover {
color: hsl(235deg, 60%, 60%);
}
/*# sourceMappingURL=dark.css.map */

View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["dark.scss"],"names":[],"mappings":"AAgCA;EACE;EACA;EACA;EACA;;;AAGF;EACE,YAvCS;EAwCT,OAdS;;;AAiBX;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EAEE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE,kBAzFS;EA0FT,cA1FS;EA2FT;;AAEA;EACE,kBA3FO;EA4FP,cA5FO;EA6FP;;;AAMJ;EACE,kBA3FQ;;AA4FR;EACE,kBA9FM;;;AAoGN;EACE,kBApGI;EAqGJ,QArGI;;AAuGJ;EACE,kBAzGE;EA0GF,QA1GE;;;AAkHN;EACE,kBA/GK;EAgHL,QAhHK;;AAkHL;EACE,kBApHG;EAqHH,QArHG;;;AA2HX;EACE,kBA3HS;;AA4HT;EACE,kBA9HO;;;AAkIX;EACE;EACA;EACA;;AAEA;EAEE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EAEE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EAEE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;;AAKF;EACE;;AACA;EACE;;AAIJ;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAKN;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE,OAnQO;;AAqQT;EACE,cAtQO;;;AA0QX;AAAA;EAEE,OAnQS;;AAqQT;AAAA;EACE,YAhSO;;;AAoSX;AAAA;AAAA;EAGE;EACA;;;AAGF;EACE,YA1SS;;;AA6SX;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AAAA;EAEE;;;AAGF;AAAA;EAEE;;;AAGF;AAAA;EAEE;;;AAGF;EACE;;;AAGF;EACE,cA5UU;;;AA+UZ;EACE;;;AAEF;EACE;EACA;;;AAGF;AAAA;EAEE;;;AAGF;EACE,cApWS;;;AAuWX;EACE,kBA5WS;;;AA+WX;AAAA;EAEE,kBAlXS;EAmXT,cA9WS;;;AAiXX;EACE,OA/VY;;;AAkWd;EACE,OAhWS;EAiWT;;AAEA;EACE,OAnWO;;;AAuWX;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAIJ;AAAA;EAEE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;AAEA;AAAA;EAEE;;;AAKJ;EACE;;;AAIA;EACE;;AAIA;EACE,OAhbK;;AAobT;EACE;;AAGF;EACE;;;AAKJ;EACE,OA1cS","file":"dark.css"}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["dark.scss"],"names":[],"mappings":"AAgCA,MACE,0CACA,yCACA,sCACA,4CAGF,KACE,WAvCS,KAwCT,MAdS,KAiBX,0CACE,yBAGF,aACE,yBACA,gCACA,8BAEA,mBACE,oCACA,gCAIJ,qBACE,yBACA,gCACA,oCAEA,uDAEE,yBAGF,4BACE,oCACA,gCAGF,2BACE,oCACA,gCAIJ,aACE,yBACA,gCACA,8BAEA,mBACE,oCACA,gCAIJ,iBACE,iBAzFS,QA0FT,aA1FS,QA2FT,WAEA,uBACE,iBA3FO,KA4FP,aA5FO,KA6FP,WAMJ,qCACE,iBA3FQ,QA4FR,2CACE,iBA9FM,KAoGN,wCACE,iBApGI,QAqGJ,OArGI,QAuGJ,8CACE,iBAzGE,KA0GF,OA1GE,KAkHN,yCACE,iBA/GK,QAgHL,OAhHK,QAkHL,+CACE,iBApHG,QAqHH,OArHG,QA2HX,kCACE,iBA3HS,QA4HT,wCACE,iBA9HO,QAkIX,qBACE,yBACA,gCACA,oCAEA,uDAEE,yBAGF,4BACE,oCACA,gCAGF,2BACE,oCACA,gCAIJ,YACE,yBACA,gCACA,8BAEA,kBACE,iCACA,6BAIJ,oBACE,yBACA,gCACA,oCAEA,qDAEE,yBAGF,2BACE,oCACA,gCAGF,0BACE,iCACA,6BAIJ,eACE,yBACA,gCACA,8BAEA,qBACE,oCACA,gCAIJ,uBACE,yBACA,gCACA,oCAEA,2DAEE,yBAGF,8BACE,oCACA,gCAGF,6BACE,oCACA,gCAKF,gCACE,yBACA,sCACE,sBAIJ,mBACE,0CAEA,uCACE,yBAGF,uCACE,yBAGF,sCACE,sBAGF,yCACE,yBAKN,cACE,oCACA,sCACA,sBAGF,uBACE,sBAGF,oBACE,8BAIA,aACE,MAnQO,QAqQT,iBACE,aAtQO,QA0QX,iDAEE,MAnQS,KAqQT,6DACE,WAhSO,KAoSX,8FAGE,oCACA,8BAGF,MACE,WA1SS,QA6SX,YACE,sBAGF,aACE,yBAGF,cACE,yBAGF,cACE,yBAGF,WACE,yBAGF,0CAEE,yBAGF,wCAEE,sBAGF,oCAEE,yBAGF,aACE,oCAGF,cACE,aA5UU,QA+UZ,6CACE,oCAEF,qCACE,oCACA,gCAGF,+DAEE,yBAGF,GACE,aApWS,QAuWX,eACE,iBA5WS,KA+WX,4BAEE,iBAlXS,QAmXT,aA9WS,QAiXX,KACE,MA/VY,QAkWd,OACE,MAhWS,KAiWT,iBAEA,aACE,MAnWO,QAuWX,2BACE,iCAGF,SACE,iCACA,uBAGF,cACE,sBAGF,UACE,oCAEA,2BACE,iCACA,sBACA,uCAGF,yBACE,oCACA,sBAGF,qCACE,yBAGF,gCACE,oCAGF,0CACE,oCAIJ,mFAEE,mCAGF,oCACE,oCAGF,0BACE,iCAGF,OACE,sBAEA,oBAEE,6BAKJ,4FACE,wDAIA,WACE,oCAIA,4BACE,MAhbK,KAobT,mCACE,yBAGF,mBACE,oCAKJ,WACE,MA1cS","file":"dark.min.css"}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,480 @@
$grey-100: #1e1e1e;
$grey-200: #222222;
$grey-300: #242424;
$grey-400: #272727;
$grey-500: #2c2c2c;
$grey-600: #2e2e2e;
$grey-700: #333333;
$grey-800: #383838;
$grey-900: #424242;
$grey-1000: #777777;
$green-400: hsl(120deg, 30%, 40%);
$green-500: hsl(120deg, 30%, 50%);
$red-400: hsl(0deg, 60%, 50%);
$red-500: hsl(0deg, 60%, 60%);
$blue-400: hsl(235deg, 60%, 50%);
$blue-500: hsl(235deg, 60%, 60%);
$blue-400-clear: rgba(51, 64, 204, 0.25);
$cyan-400: hsl(190deg, 60%, 50%);
$cyan-500: hsl(190deg, 60%, 60%);
$magenta-500: hsl(315deg, 60%, 60%);
$text-100: hsl(0deg, 0%, 90%);
$text-200: hsl(0deg, 0%, 80%);
$text-300: hsl(0deg, 0%, 70%);
$text-400: hsl(0deg, 0%, 60%);
$text-500: hsl(0deg, 0%, 50%);
:root {
--green: #{$green-500} !important;
--blue: #{$blue-500} !important;
--red: #{$red-500} !important;
--magenta: #{$magenta-500} !important;
}
body {
background: $grey-200;
color: $text-200;
}
a.text-primary:focus, a.text-primary:hover{
color: $blue-400 !important;
}
.btn-primary {
color: $text-100 !important;
border-color: $blue-500 !important;
background: $blue-500 !important;
&:hover {
background-color: $blue-400 !important;
border-color: $blue-400 !important;
}
}
.btn-outline-primary {
color: $blue-500 !important;
border-color: $blue-500 !important;
background: transparent !important;
&:hover,
&.active {
color: $text-100 !important;
}
&.active {
background-color: $blue-500 !important;
border-color: $blue-500 !important;
}
&:hover {
background-color: $blue-400 !important;
border-color: $blue-400 !important;
}
}
.btn-success {
color: $text-100 !important;
border-color: $green-500 !important;
background: $green-500 !important;
&:hover {
background-color: $green-400 !important;
border-color: $green-400 !important;
}
}
.list-group-item{
background-color: $grey-400;
border-color: $grey-400;
color: white;
&:hover{
background-color: $grey-700;
border-color: $grey-700;
color: white;
}
}
.delete-peer-bulk-badge.badge-danger{
background-color: $red-500;
&:hover{
background-color: $red-400;
}
}
#delete_bulk_modal{
.list-group{
a.active{
background-color: $red-500;
border: $red-500;
&:hover{
background-color: $red-400;
border: $red-400;
}
}
}
}
#available_ip_modal{
.list-group{
a.active{
background-color: $blue-500;
border: $blue-500;
&:hover{
background-color: $blue-400;
border: $blue-400;
}
}
}
}
.available-ip-badge.badge-primary{
background-color: $blue-500;
&:hover{
background-color: $blue-400;
}
}
.btn-outline-success {
color: $green-500 !important;
border-color: $green-500 !important;
background: transparent !important;
&:hover,
&.active {
color: $text-100 !important;
}
&.active {
background-color: $green-500 !important;
border-color: $green-500 !important;
}
&:hover {
background-color: $green-400 !important;
border-color: $green-400 !important;
}
}
.btn-danger {
color: $text-100 !important;
border-color: $red-500 !important;
background: $red-500 !important;
&:hover {
background-color: $red-400 !important;
border-color: $red-400 !important;
}
}
.btn-outline-danger {
color: $red-500 !important;
border-color: $red-500 !important;
background: transparent !important;
&:hover,
&.active {
color: $text-100 !important;
}
&.active {
background-color: $red-500 !important;
border-color: $red-500 !important;
}
&:hover {
background-color: $red-400 !important;
border-color: $red-400 !important;
}
}
.btn-secondary {
color: $text-100 !important;
border-color: $grey-900 !important;
background: $grey-900 !important;
&:hover {
background-color: $grey-800 !important;
border-color: $grey-800 !important;
}
}
.btn-outline-secondary {
color: $grey-900 !important;
border-color: $grey-900 !important;
background: transparent !important;
&:hover,
&.active {
color: $text-100 !important;
}
&.active {
background-color: $grey-900 !important;
border-color: $grey-900 !important;
}
&:hover {
background-color: $grey-800 !important;
border-color: $grey-800 !important;
}
}
.btn-control {
&.btn-lock-peer.lock {
color: $red-500 !important;
&:hover {
color: $red-400 !important;
}
}
&:hover {
background-color: transparent !important;
&.btn-outline-primary {
color: $blue-400 !important;
}
&.btn-outline-success {
color: $green-400 !important;
}
&.btn-outline-danger {
color: $red-400 !important;
}
&.btn-outline-secondary {
color: $grey-800 !important;
}
}
}
.form-control {
background-color: $grey-500 !important;
border-color: transparent !important;
color: $text-200 !important;
}
.form-control:disabled{
color: $grey-1000 !important;
}
.card .form-control {
background: $grey-500 !important;
}
.conf_card{
a{
color: $blue-500;
}
&:hover{
border-color: $blue-500;
}
}
.sidebar .nav-link,
.bottomNavContainer .nav-link {
color: $text-200;
&:hover {
background: $grey-200;
}
}
nav#sidebarMenu.col-md-3.col-lg-2.d-md-block.bg-light.sidebar.collapse,
.navbar-brand,
.bg-dark {
background-color: $grey-100 !important;
background: $grey-100 !important;
}
.card {
background: $grey-400;
}
.text-muted {
color: $text-500 !important;
}
.text-danger {
color: $red-500 !important;
}
.text-success {
color: $green-500 !important;
}
.text-primary {
color: $blue-500 !important;
}
.text-info {
color: $cyan-500 !important;
}
a.text-success:focus,
a.text-success:hover {
color: $green-400 !important;
}
a.text-danger:focus,
a.text-danger:hover {
color: $red-400 !important;
}
a.text-info:focus,
a.text-info:hover {
color: $cyan-400 !important;
}
.dot-running {
background-color: $green-500 !important;
}
.card-running {
border-color: $green-500;
}
.toggle--switch:checked + .toggleLabel::before {
background-color: $blue-500 !important;
}
.toggle--switch:checked + .toggleLabel {
background-color: mix($blue-500, #000f) !important;
border-color: $blue-500 !important;
}
.sidebar .nav-link.active,
.bottomNavContainer .nav-link.active {
color: $blue-500 !important;
}
hr {
border-color: $grey-600;
}
.modal-content {
background-color: $grey-200;
}
.modal-header,
.modal-footer {
background-color: $grey-100;
border-color: $grey-600;
}
code {
color: $magenta-500;
}
.close {
color: $text-200;
text-shadow: none;
&:hover {
color: $text-300;
}
}
.chartContainer.fullScreen {
background-color: $grey-200 !important;
}
.popover {
background-color: $grey-700 !important;
border: none !important;
}
.popover-body {
color: $text-200 !important;
}
div.toast {
background-color: $grey-900 !important;
div.toast-header {
background-color: $grey-700 !important;
color: $text-200 !important;
border-bottom-color: $grey-900 !important;
}
div.toast-body {
background-color: $grey-800 !important;
color: $text-200 !important;
}
div.toast-body.text-danger{
color: $red-500 !important;
}
div.toast-progressbar {
background-color: $blue-500 !important;
}
div.toast-progressbar.bg-danger {
background-color: $red-500 !important;
}
}
.bs-popover-auto[x-placement^="right"] > .arrow::after,
.bs-popover-right > .arrow::after {
border-right-color: $grey-700 !important;
}
.btn-manage-group .setting_btn_menu {
background-color: $grey-500 !important;
}
.setting_btn_menu a:hover {
background-color: $grey-700 !important;
}
.table {
color: $text-200 !important;
th,
td {
border-color: $grey-700 !important;
}
}
.btn-outline-primary.focus, .btn-outline-primary:focus, .btn-primary.focus, .btn-primary:focus{
box-shadow: 0 0 0 0.2rem rgb(144 153 255 / 29%) !important;
}
.bottomNav{
&{
background-color: $grey-400 !important;
}
.bottomNavButton{
&{
color: $text-400;
}
}
.bottomNavButton.active{
color: $blue-500 !important;
}
.subNav{
background-color: $grey-400 !important;
}
}
.key:hover{
color: $blue-500;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,138 @@
let numberToast = 0,
emptyInputFeedback = "Can't leave empty";
$('[data-toggle="tooltip"]').tooltip();
let $add_configuration = $("#add_configuration"),
addConfigurationModal = $("#addConfigurationModal");
function showToast(a) {
$(".toastContainer").append(`<div id="${numberToast}-toast" class="toast hide" role="alert" data-delay="500">
<div class="toast-header">
<strong class="mr-auto">WGDashboard</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">${a}</div>
<div class="toast-progressbar"></div>
</div>`), $(`#${numberToast}-toast`).toast("show"), $(`#${numberToast}-toast .toast-body`).html(a), $(`#${numberToast}-toast .toast-progressbar`).css("transition", `width ${$(`#${numberToast}-toast .toast-progressbar`).parent().data("delay")}ms cubic-bezier(0, 0, 0, 0)`), $(`#${numberToast}-toast .toast-progressbar`).css("width", "0px"), numberToast++
}
function genKeyPair() {
let a = window.wireguard.generateKeypair();
$("#addConfigurationPrivateKey").val(a.privateKey).data("checked", !0)
}
function ajaxPostJSON(a, t, e) {
$.ajax({
url: a,
method: "POST",
data: JSON.stringify(t),
headers: {
"Content-Type": "application/json"
}
}).done(function(a) {
e(a)
})
}
function validInput(a) {
a.removeClass("is-invalid").addClass("is-valid").removeAttr("disabled").data("checked", !0)
}
function invalidInput(a, t, e) {
a.removeClass("is-valid").addClass("is-invalid").removeAttr("disabled").data("checked", !1), t.addClass("invalid-feedback").text(e)
}
function checkPort(a) {
let t = a;
t.attr("disabled", "disabled");
let e = $("#addConfigurationListenPortFeedback");
0 == t.val().length ? invalidInput(t, e, emptyInputFeedback) : ajaxPostJSON("/api/addConfigurationPortCheck", {
port: t.val()
}, function a(i) {
i.status ? validInput(t) : invalidInput(t, e, i.reason)
})
}
function checkAddress(a) {
let t = a;
t.attr("disabled", "disabled");
let e = $(".addConfigurationAvailableIPs"),
i = $("#addConfigurationAddressFeedback");
0 == t.val().length ? (invalidInput(t, i, emptyInputFeedback), e.html("N/A")) : ajaxPostJSON("/api/addConfigurationAddressCheck", {
address: t.val()
}, function a(n) {
n.status ? (e.html(`<strong>${n.data}</strong>`), validInput(t)) : (invalidInput(t, i, n.reason), e.html("N/A"))
})
}
function checkName(a) {
let t = a,
e = $("#addConfigurationNameFeedback");
t.val(t.val().replace(/\s/g, "")).attr("disabled", "disabled"), 0 === t.val().length ? invalidInput(t, e, emptyInputFeedback) : ajaxPostJSON("/api/addConfigurationNameCheck", {
name: t.val()
}, function a(i) {
i.status ? validInput(t) : invalidInput(t, e, i.reason)
})
}
addConfigurationModal.modal({
keyboard: !1,
backdrop: "static",
show: !1
}), addConfigurationModal.on("hidden.bs.modal", function() {
$("#add_configuration_form").trigger("reset"), $("#add_configuration_form input").removeClass("is-valid").removeClass("is-invalid"), $(".addConfigurationAvailableIPs").text("N/A")
}), $(".toggle--switch").on("change", function() {
$(this).addClass("waiting").attr("disabled", "disabled");
let a = $(this).data("conf-id"),
t = $(this).prop("checked"),
e = $(this);
$(this).siblings("label"), $.ajax({
url: `/switch/${a}`
}).done(function(i) {
let n = $(`div[data-conf-id="${a}"] .dot`);
i.status ? t ? (n.removeClass("dot-stopped").addClass("dot-running"), n.siblings().text("Running"), showToast(`${a} is running.`)) : (n.removeClass("dot-running").addClass("dot-stopped"), showToast(`${a} is stopped.`)) : (e.parents().children(".card-message").html(`<pre class="index-alert">Configuration toggle failed. Please check the following error message:<br><code>${i.message}</code></pre>`), t ? e.prop("checked", !1) : e.prop("checked", !0)), e.removeClass("waiting").removeAttr("disabled")
})
}), $(".sb-home-url").addClass("active"), $(".card-body").on("click", function(a) {
"toggleLabel" !== $(a.target).attr("class") && "toggle--switch" !== $(a.target).attr("class") && window.open($(this).find("a").attr("href"), "_self")
}), $("#reGeneratePrivateKey").on("click", function() {
genKeyPair()
}), $("#toggleAddConfiguration").on("click", function() {
addConfigurationModal.modal("toggle"), genKeyPair()
}), $("#addConfigurationPrivateKey").on("change", function() {
$privateKey = $(this), $privateKeyFeedback = $("#addConfigurationPrivateKeyFeedback"), 44 != $privateKey.val().length ? invalidInput($privateKey, $privateKeyFeedback, "Invalid length") : validInput($privateKey)
}), $("#addConfigurationListenPort").on("change", function() {
checkPort($(this))
}), $("#addConfigurationAddress").on("change", function() {
checkAddress($(this))
}), $("#addConfigurationName").on("change", function() {
checkName($(this))
}), $("#addConfigurationBtn").on("click", function() {
$(this);
let a = $("#add_configuration_form input"),
t = !0;
for (let e = 0; e < a.length; e++) {
let i = $(a[e]);
void 0 == i.attr("required") || (0 == i.val().length && "addConfigurationPrivateKey" !== i.attr("name") && (invalidInput(i, i.siblings(".input-feedback"), emptyInputFeedback), t = !1), 44 != i.val().length && "addConfigurationPrivateKey" == i.attr("name") && (invalidInput(i, i.siblings(".input-feedback"), "Invalid length"), t = !1), i.data("checked") || (t = !1))
}
if (t) {
$("#addConfigurationModal .modal-footer .btn").hide(), $(".addConfigurationStatus").removeClass("d-none");
let n = {},
o = [];
for (let d = 0; d < a.length; d++) {
let s = $(a[d]);
n[s.attr("name")] = s.val(), o.push(s.attr("name"))
}
ajaxPostJSON("/api/addConfiguration", n, a => {
let t = a.data;
$(".addConfigurationAddStatus").removeClass("text-primary").addClass("text-success").html(`<i class="bi bi-check-circle-fill"></i> ${t} added successfully.`), a.status ? setTimeout(() => {
$(".addConfigurationToggleStatus").removeClass("waiting").html('<div class="spinner-border spinner-border-sm" role="status"></div> Toggle Configuration'), $.ajax({
url: `/switch/${t}`
}).done(function(a) {
a.status ? ($(".addConfigurationToggleStatus").removeClass("text-primary").addClass("text-success").html('<i class="bi bi-check-circle-fill"></i> Toggle Successfully. Refresh in 5 seconds.'), setTimeout(() => {
$(".addConfigurationToggleStatus").text("Refeshing..."), location.reload()
}, 5e3)) : ($(".addConfigurationToggleStatus").removeClass("text-primary").addClass("text-danger").html(`<i class="bi bi-x-circle-fill"></i> ${t} toggle failed.`), $("#addCconfigurationAlertMessage").removeClass("d-none").html(`${t} toggle failed. Please check the following error message:<br>${a.message}`))
})
}, 500) : ($(".addConfigurationStatus").removeClass("text-primary").addClass("text-danger").html(`<i class="bi bi-x-circle-fill"></i> ${t} adding failed.`), $("#addCconfigurationAlert").removeClass("d-none").children(".alert-body").text(a.reason))
})
}
});

10
WG-Dash/src/static/js/index.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,59 @@
$(".sb-settings-url").addClass("active");
$(".confirm_modal").click(function () {
$(".app_new_ip").text($("#app_ip")[0].value);
$(".app_new_port").text($("#app_port")[0].value);
});
$(".confirm_restart").click(function () {
$(".cancel_restart").remove();
countdown = 7;
$.post('/update_app_ip_port', $('.update_app_ip_port').serialize());
url = $("#app_ip")[0].value + ":" + $("#app_port")[0].value;
$(".confirm_restart").attr("disabled", "disabled");
setInterval(function () {
if (countdown === 0) {
window.location.replace("http://" + url);
}
$(".confirm_restart").text("Redirecting you in " + countdown + " seconds.");
countdown--;
}, 1000);
});
$(".change_path").click(function () {
$(this).attr("disabled", "disabled");
countdown = 5;
setInterval(function () {
if (countdown === 0) {
location.reload();
}
$(".change_path").text("Redirecting you in " + countdown + " seconds.");
countdown--;
}, 1000);
$.post('/update_wg_conf_path', $('.update_wg_conf_path').serialize());
});
$(".bottomNavSettings").addClass("active");
$(".theme-switch-btn").on("click", function(){
if (!$(this).hasClass("active")){
let theme = $(this).data("theme");
$(".theme-switch-btn").removeClass("active");
$(this).addClass("active");
$.ajax({
method: "POST",
url: "/api/settings/setTheme",
headers: {"Content-Type": "application/json"},
data: JSON.stringify({"theme": theme})
}).done(function(res){
if (res.status == true){
if (theme == "light"){
$("#darkThemeCSS").remove();
} else {
$("head").append('<link rel="stylesheet" type="text/css" href="/static/css/theme/dark.min.css" id="darkThemeCSS">');
}
}
});
}
});

1
WG-Dash/src/static/js/settings.min.js vendored Normal file
View File

@ -0,0 +1 @@
$(".sb-settings-url").addClass("active"),$(".confirm_modal").click(function(){$(".app_new_ip").html($("#app_ip")[0].value),$(".app_new_port").html($("#app_port")[0].value)}),$(".confirm_restart").click(function(){$(".cancel_restart").remove(),countdown=7,$.post("/update_app_ip_port",$(".update_app_ip_port").serialize()),url=$("#app_ip")[0].value+":"+$("#app_port")[0].value,$(".confirm_restart").attr("disabled","disabled"),setInterval(function(){0===countdown&&window.location.replace("http://"+url),$(".confirm_restart").html("Redirecting you in "+countdown+" seconds."),countdown--},1e3)}),$(".change_path").click(function(){$(this).attr("disabled","disabled"),countdown=5,setInterval(function(){0===countdown&&location.reload(),$(".change_path").html("Redirecting you in "+countdown+" seconds."),countdown--},1e3),$.post("/update_wg_conf_path",$(".update_wg_conf_path").serialize())}),$(".bottomNavSettings").addClass("active"),$(".theme-switch-btn").on("click",function(){if(!$(this).hasClass("active")){let t=$(this).data("theme");$(".theme-switch-btn").removeClass("active"),$(this).addClass("active"),$.ajax({method:"POST",url:"/api/settings/setTheme",headers:{"Content-Type":"application/json"},data:JSON.stringify({theme:t})}).done(function(e){!0==e.status&&("light"==t?$("#darkThemeCSS").remove():$("head").append('<link rel="stylesheet" type="text/css" href="/static/css/theme/dark.min.css" id="darkThemeCSS">'))})}});

View File

@ -0,0 +1,67 @@
/**
* tools.js - Copyright(C) 2021 Donald Zou [https://github.com/donaldzou]
*/
$(".ip_dropdown").on("change",function (){
$(".modal.show .btn").removeAttr("disabled");
});
$(".conf_dropdown").on("change", function (){
$(".modal.show .ip_dropdown").html('<option value="none" selected="selected" disabled>Loading...');
$.ajax({
url: "/get_ping_ip",
method: "POST",
data: "config=" + $(this).children("option:selected").val(),
success: function (res){
$(".modal.show .ip_dropdown").html("");
$(".modal.show .ip_dropdown").append('<option value="none" selected="selected" disabled>Choose an IP');
$(".modal.show .ip_dropdown").append(res);
}
});
});
// Ping Tools
$(".send_ping").on("click", function (){
$(this).attr("disabled","disabled");
$(this).html("Pinging...");
$("#ping_modal .form-control").attr("disabled","disabled");
$.ajax({
method:"POST",
data: "ip="+ $(':selected', $("#ping_modal .ip_dropdown")).val() +
"&count=" + $("#ping_modal .ping_count").val(),
url: "/ping_ip",
success: function (res){
$(".ping_result tbody").html("");
let html = '<tr><th scope="row">Address</th><td>'+res.address+'</td></tr>' +
'<tr><th scope="row">Is Alive</th><td>'+res.is_alive+'</td></tr>' +
'<tr><th scope="row">Min RTT</th><td>'+res.min_rtt+'ms</td></tr>' +
'<tr><th scope="row">Average RTT </th><td>'+res.avg_rtt+'ms</td></tr>' +
'<tr><th scope="row">Max RTT</th><td>'+res.max_rtt+'ms</td></tr>' +
'<tr><th scope="row">Package Sent</th><td>'+res.package_sent+'</td></tr>' +
'<tr><th scope="row">Package Received</th><td>'+res.package_received+'</td></tr>' +
'<tr><th scope="row">Package Loss</th><td>'+res.package_loss+'</td></tr>';
$(".ping_result tbody").html(html);
$(".send_ping").removeAttr("disabled");
$(".send_ping").html("Ping");
$("#ping_modal .form-control").removeAttr("disabled");
}
});
});
// Traceroute Tools
$(".send_traceroute").on("click", function (){
$(this).attr("disabled","disabled");
$(this).html("Tracing...");
$("#traceroute_modal .form-control").attr("disabled","disabled");
$.ajax({
url: "/traceroute_ip",
method: "POST",
data: "ip=" + $(':selected', $("#traceroute_modal .ip_dropdown")).val(),
success: function (res){
$(".traceroute_result tbody").html("");
res.forEach((ele) =>
$(".traceroute_result tbody").append('<tr><th scope="row">'+ele.hop+'</th><td>'+ele.ip+'</td><td>'+ele.avg_rtt+'</td><td>'+ele.min_rtt+'</td><td>'+ele.max_rtt+'</td></tr>'));
$(".send_traceroute").removeAttr("disabled").html("Traceroute");
$("#traceroute_modal .form-control").removeAttr("disabled");
}
});
});

1
WG-Dash/src/static/js/tools.min.js vendored Normal file
View File

@ -0,0 +1 @@
$(".ip_dropdown").on("change",function(){$(".modal.show .btn").removeAttr("disabled")}),$(".conf_dropdown").on("change",function(){$(".modal.show .ip_dropdown").html('<option value="none" selected="selected" disabled>Loading...'),$.ajax({url:"/get_ping_ip",method:"POST",data:"config="+$(this).children("option:selected").val(),success:function(t){$(".modal.show .ip_dropdown").html(""),$(".modal.show .ip_dropdown").append('<option value="none" selected="selected" disabled>Choose an IP'),$(".modal.show .ip_dropdown").append(t)}})}),$(".send_ping").on("click",function(){$(this).attr("disabled","disabled"),$(this).html("Pinging..."),$("#ping_modal .form-control").attr("disabled","disabled"),$.ajax({method:"POST",data:"ip="+$(":selected",$("#ping_modal .ip_dropdown")).val()+"&count="+$("#ping_modal .ping_count").val(),url:"/ping_ip",success:function(t){$(".ping_result tbody").html("");let e='<tr><th scope="row">Address</th><td>'+t.address+'</td></tr><tr><th scope="row">Is Alive</th><td>'+t.is_alive+'</td></tr><tr><th scope="row">Min RTT</th><td>'+t.min_rtt+'ms</td></tr><tr><th scope="row">Average RTT </th><td>'+t.avg_rtt+'ms</td></tr><tr><th scope="row">Max RTT</th><td>'+t.max_rtt+'ms</td></tr><tr><th scope="row">Package Sent</th><td>'+t.package_sent+'</td></tr><tr><th scope="row">Package Received</th><td>'+t.package_received+'</td></tr><tr><th scope="row">Package Loss</th><td>'+t.package_loss+"</td></tr>";$(".ping_result tbody").html(e),$(".send_ping").removeAttr("disabled"),$(".send_ping").html("Ping"),$("#ping_modal .form-control").removeAttr("disabled")}})}),$(".send_traceroute").on("click",function(){$(this).attr("disabled","disabled"),$(this).html("Tracing..."),$("#traceroute_modal .form-control").attr("disabled","disabled"),$.ajax({url:"/traceroute_ip",method:"POST",data:"ip="+$(":selected",$("#traceroute_modal .ip_dropdown")).val(),success:function(t){$(".traceroute_result tbody").html(""),t.forEach(t=>$(".traceroute_result tbody").append('<tr><th scope="row">'+t.hop+"</th><td>"+t.ip+"</td><td>"+t.avg_rtt+"</td><td>"+t.min_rtt+"</td><td>"+t.max_rtt+"</td></tr>")),$(".send_traceroute").removeAttr("disabled").html("Traceroute"),$("#traceroute_modal .form-control").removeAttr("disabled")}})});

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].content);
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){
var files = res.peers;
var zipFile = createZipFile(files);
var blob = new Blob([zipFile], { type: "application/zip" });
var a = document.createElement("a");
a.download = res.filename;
a.href = URL.createObjectURL(blob);
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
};
})();

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,31 @@
{
"theme_color": "#343a40",
"background_color": "#343a40",
"display": "fullscreen",
"scope": "/",
"start_url": "/",
"name": "WireGate",
"short_name": "WireGate",
"icons": [
{
"src": "/static/img/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/img/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "/static/img/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/static/img/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@ -0,0 +1,467 @@
<!-- configuration.html - < WGDashboard > - Copyright(C) 2021 Donald Zou [https://github.com/donaldzou]-->
<html lang="en">
{% with title=title%}
{% include "header.html"%}
{% endwith %}
<body>
<div class="no-response">
<div class="container">
<h1 class="text-white display-1"><i class="bi bi-emoji-frown-fill"></i></h1>
<h4 class="text-white">Oops!<br>I can't connect to the server.</h4>
</div>
</div>
{% include "navbar.html" %}
<div class="container-fluid" id="right_body">
{% include "sidebar.html" %}
<div class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4 mb-4">
</div>
<div id="config_body">
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4 mb-4">
<div class="info mt-4">
<div>
<pre class="index-alert d-none" style="margin-bottom: 1rem;"></pre>
</div>
<div class="row">
<div class="col">
<small class="text-muted"><strong>ZONE</strong></small>
<h1 class="mb-3"><samp id="conf_name">{{ title }}</samp></h1>
</div>
<div class="col">
<small class="text-muted"><strong>SWITCH</strong></small><br>
<!-- <div id="conf_status_btn" class="info_loading"></div> -->
<div id="switch" class="info_loading">
<input type="checkbox" class="toggle--switch" id="toggle--switch">
<label for="toggle--switch" class="toggleLabel"></label>
</div>
</div>
<div class="w-100"></div>
<div class="col">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;" id="conf_status" class="info_loading"></h6>
</div>
<div class="col">
<small class="text-muted"><strong>CONNECTED PEERS</strong></small>
<h6 style="text-transform: uppercase;" id="conf_connected_peers" class="info_loading"></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL DATA USAGE</strong></small>
<h6 style="text-transform: uppercase;" id="conf_total_data_usage" class="info_loading"></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL RECEIVED</strong></small>
<h6 style="text-transform: uppercase;" id="conf_total_data_received" class="info_loading"></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL SENT</strong></small>
<h6 style="text-transform: uppercase;" id="conf_total_data_sent" class="info_loading"></h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted">
<strong>PUBLIC KEY</strong>
<strong style="margin-left: auto!important; opacity: 0; transition: 0.2s ease-in-out" class="text-primary">CLICK TO COPY</strong>
</small>
<h6 class="info_loading"><samp class="key" id="conf_public_key"></samp></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>LISTEN PORT</strong></small>
<h6 style="text-transform: uppercase;" class="info_loading"><samp id="conf_listen_port"></samp></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>ADDRESS</strong></small>
<h6 style="text-transform: uppercase;" class="info_loading"><samp id="conf_address"></samp></h6>
</div>
</div>
</div>
<hr>
<div class="row chartContainer">
<div class="col-sm">
<div class="chartTitle">
<h6>Data Usage / Refresh Interval</h6>
<div class="chartControl" style="margin-left: auto">
<div class="btn-group" role="group">
<button class="btn btn-outline-primary btn-sm switchUnit" data-unit="GB">GB</button>
<button class="btn btn-outline-primary btn-sm switchUnit" data-unit="MB">MB</button>
<button class="btn btn-outline-primary btn-sm switchUnit" data-unit="KB">KB</button>
<button class="btn btn-outline-primary btn-sm fullScreen"><i class="bi bi-fullscreen"></i></button>
</div>
</div>
</div>
<div class="chartCanvasContainer" style="width: 100%; height: 300px">
<canvas id="totalDataUsageChartObj" width="100" height="100"></canvas>
</div>
</div>
</div>
<hr>
<div class="button-div mb-3">
<div class="row">
<div class="col-sm">
<div class="row">
<div class="col-sm">
<div class="form-group">
<label for="search_peer_textbox"><small class="text-muted">Search Peers</small></label>
<input type="text" class="form-control" id="search_peer_textbox" placeholder="Enter Peer's Name" value="">
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label for="sort_by_dropdown"><small class="text-muted">Sort Peers By</small></label>
<select class="form-control" id="sort_by_dropdown">
<option value="status">Status</option>
<option value="name">Name</option>
<option value="allowed_ip">Allowed IP</option>
</select>
</div>
</div>
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label><small class="text-muted">Refresh Interval</small></label><br>
<div class="btn-group interval-btn-group" role="group" style="width: 100%">
<button style="width: 20%" type="button" class="btn btn-outline-primary btn-group-label refresh" data-toggle="tooltip" data-placement="bottom" title="Refresh Peers"><i class="bi bi-arrow-repeat"></i></button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="5000">5s</button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="10000">10s</button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="30000">30s</button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval" data-refresh-interval="60000">1m</button>
</div>
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label><small class="text-muted">Display Mode</small></label><br>
<div class="btn-group display-btn-group" role="group" style="width: 100%">
<button style="width: 20%" type="button" class="btn btn-outline-primary display_mode" data-display-mode="grid"><i class="bi bi-grid-fill" style="font-size: 1.5rem;"></i></button>
<button style="width: 20%" type="button" class="btn btn-outline-primary display_mode" data-display-mode="list"><i class="bi bi-list" style="font-size: 1.5rem;"></i></button>
</div>
</div>
</div>
<div class="btn-manage-group">
<button type="button" class="btn btn-primary add_btn"><i class="bi bi-plus-circle-fill" style=""></i></button>
<button type="button" class="btn btn-secondary setting_btn"><i class="bi bi-three-dots"></i></button>
<div class="setting_btn_menu">
<a class="text-danger" id="delete_peers_by_bulk_btn"><i class="bi bi-trash-fill"></i> Delete Peers</a>
<a class="text-info" id="download_all_peers" data-url="/download_all/{{conf_data['name']}}"><i class="bi bi-cloud-download-fill"></i> Download All Peers</a>
<hr>
<a class="text-primary" id="configuration_setting"><i class="bi bi-gear-fill"></i> Configration Settings</a>
<a class="text-danger" id="configuration_delete"><i class="bi bi-trash3-fill"></i> Delete Configuration</a>
</div>
</div>
</div>
</div>
<div class="row peer_list"></div>
<small id="peer_loading_time" class="text-muted"></small>
</main>
</div>
</div>
<div class="modal fade" id="add_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Add New Peer</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div>
<div class="custom-control custom-switch" style="margin-bottom: 1rem">
<input class="custom-control-input" type="checkbox" id="bulk_add">
<label class="custom-control-label" for="bulk_add"><strong>Add Peers by bulk</strong></label>
<i class="bi bi-question-circle-fill" style="cursor: pointer" data-container="body" data-toggle="popover" data-placement="right" data-trigger="click" data-content="By adding peers by bulk, each peer's name will be auto generated, and Allowed IP will be assign to the next available IP."></i>
</div>
<div class="form-group" style="margin: 0">
<input type="number" class="form-control" id="new_add_amount" min="1" placeholder="Amount" disabled>
<div id="bulk_amount_validation" class="invalid-feedback"></div>
</div>
</div>
<hr>
<div id="add_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="add_peer_form">
<div class="form-group">
<div>
<label for="private_key">Private Key</label>
</div>
<div class="input-group">
<input type="text" class="form-control non-bulk" id="private_key" aria-describedby="private_key">
<div class="input-group-append">
<button type="button" class="btn btn-danger non-bulk" id="re_generate_key" data-toggle="tooltip" data-placement="top" title="Regenerate Key"><i class="bi bi-arrow-repeat"></i></button>
</div>
</div>
</div>
<div class="form-group">
<label for="public_key">Public Key <code>(Required)</code></label>
<input type="text" class="form-control non-bulk" id="public_key" aria-describedby="public_key" disabled>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="new_add_name">Name</label>
<input type="text" class="form-control non-bulk" id="new_add_name">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="allowed_ips">Allowed IPs <code>(Required)</code></label>
<div class="input-group">
<input type="text" class="form-control non-bulk" id="allowed_ips">
<div class="input-group-append">
<button type="button" class="btn btn-primary non-bulk" id="search_available_ip" data-toggle="tooltip" data-placement="top" title="Search Available IPs">
<i class="bi bi-search"></i>
</button>
</div>
</div>
<p style="position: absolute; top: 4px; right: 1rem;" class="text-success" id="allowed_ips_indicator"></p>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="new_add_DNS">DNS <code>(Required)</code></label>
<input type="text" class="form-control" id="new_add_DNS" value="{{ DNS }}">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="new_add_endpoint_allowed_ip">Endpoint Allowed IPs <code>(Required)</code></label>
<input type="text" class="form-control" id="new_add_endpoint_allowed_ip" value="{{ endpoint_allowed_ip }}">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="new_add_MTU">MTU</label>
<input type="text" class="form-control" id="new_add_MTU" value="{{ mtu }}">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="new_add_keep_alive">Persistent keepalive</label>
<input type="text" class="form-control" id="new_add_keep_alive" value="{{ keep_alive }}">
</div>
</div>
<div class="col-sm">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enable_preshare_key" name="enable_preshare_key" value="enable_psk">
<label class="form-check-label" for="enable_preshare_key">Use Pre-shared Key</label>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save_peer" conf_id={{conf_data['name']}}>Add</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="configuration_delete_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Are you sure to delete this configuration?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="remove_configuration_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert">
</div>
<p style="margin: 0">This action is not reversible. The configuration will get toggle off, and delete from database and from the configuration folder.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">No</button>
<button type="button" class="btn btn-danger" id="sure_delete_configuration">Yes</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="delete_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Are you sure to delete this peer?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="remove_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<h6 style="margin: 0">This action is not reversible.</h6>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">No</button>
<button type="button" class="btn btn-danger" id="delete_peer" conf_id={{conf_data['name']}} peer_id="">Yes</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="setting_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true" conf_id={{conf_data['name']}} peer_id="">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title peer_name"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="setting_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="mb-3">
<label for="peer_private_key_textbox" class="form-label">Private Key <code>(Required for QR Code and download)</code></label>
<input type="password" class="form-control" id="peer_private_key_textbox" style="padding-right: 40px">
<a class="peer_private_key_textbox_switch"><i class="bi bi-eye-fill"></i></a>
</div>
<div>
<label for="peer_preshared_key_textbox" class="form-label">Pre-Shared Key</label>
<input type="text" class="form-control" id="peer_preshared_key_textbox">
</div>
<hr>
<div class="row">
<div class="col-sm-6">
<div class="mb-3">
<label for="peer_name_textbox" class="form-label">Name</label>
<input type="text" class="form-control" id="peer_name_textbox" placeholder="">
</div>
</div>
<div class="col-sm-6">
<div class="mb-3">
<label for="peer_allowed_ip_textbox" class="form-label">Allowed IPs <code>(Required)</code></label>
<input type="text" class="form-control" id="peer_allowed_ip_textbox">
</div>
</div>
<div class="col-sm-6">
<div class="mb-3">
<label for="peer_DNS_textbox" class="form-label">DNS <code>(Required)</code></label>
<input type="text" class="form-control" id="peer_DNS_textbox">
</div>
</div>
<div class="col-sm-6">
<div class="mb-3">
<label for="peer_endpoint_allowed_ips" class="form-label">Endpoint Allowed IPs <code>(Required)</code></label>
<input type="text" class="form-control" id="peer_endpoint_allowed_ips">
</div>
</div>
<div class="col-sm-6">
<div class="mb-3">
<label for="peer_mtu" class="form-label">MTU</label>
<input type="text" class="form-control" id="peer_mtu">
</div>
</div>
<div class="col-sm-6">
<div class="mb-3">
<label for="peer_keep_alive" class="form-label">Persistent Keepalive</label>
<input type="text" class="form-control" id="peer_keep_alive">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save_peer_setting" data-conf-id="{{conf_data['name']}}" data-peer-id="">Save</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="available_ip_modal" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Select available IP</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="selected_ip" style="padding: 1rem; border-bottom: 1px solid #dee2e6;">
<small class="text-muted"><strong>SELECTED IP (CLICK TO REMOVE)</strong></small>
<div id="selected_ip_list"></div>
</div>
<div class="modal-body" style="max-height: 400px; overflow-y: scroll;">
<div class="list-group"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm_ip">Confirm</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="delete_bulk_modal" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Select Peers to Delete</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div id="bulk_remove_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert" style="margin: 1rem">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="selected_peers" style="padding: 1rem; border-bottom: 1px solid #dee2e6;">
<small class="text-muted"><strong>SELECTED PEERS (CLICK TO REMOVE)</strong></small>
<div id="selected_peer_list"></div>
</div>
<div class="modal-body" style="max-height: 400px; overflow-y: scroll;">
<div class="list-group"></div>
</div>
<div class="modal-footer">
<a class="text-danger" id="select_all_delete_bulk_peers" style="cursor: pointer; margin-right: auto;"><small><strong>SELECT ALL</strong></small></a>
<button type="button" class="btn btn-danger" id="confirm_delete_bulk_peers" disabled data-conf="{{conf_data['name']}}">Delete</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="qrcode_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">QR Code</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<img id="qrcode_img" style="width: 100%">
</div>
</div>
</div>
</div>
<div class="position-fixed top-0 right-0 p-3 toastContainer" style="z-index: 5; right: 0; top: 50px;"></div>
{% include "tools.html" %}
</body>
{% include "footer.html" %}
<script src="{{ url_for('static',filename='js/configuration.js') }}"></script>
<script src="{{ url_for('static',filename='js/wireguard.min.js') }}"></script>
<script>
configurations.setConfigurationName("{{ conf_data['name'] }}");
configurations.setActiveConfigurationName();
configurations.loadPeers($('#search_peer_textbox').val());
</script>
</html>

View File

@ -0,0 +1,2 @@
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
<script src="{{ url_for('static',filename='js/tools.min.js') }}"></script>

View File

@ -0,0 +1,184 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4 mb-4">
<div class="info mt-4">
{% if conf_data['listen_port'] == "" and conf_data['status'] == "stopped" %}
<div class="alert alert-warning" role="alert">Peer QR Code and configuration file download required a specified <strong>Listen Port</strong>.</div>
{% endif %}
{% if conf_data['conf_address'] == "N/A" %}
<div class="alert alert-warning" role="alert">
Configuration <strong>Address</strong> not be specified to have peer connect to it.
</div>
{% endif %}
<div class="row">
<div class="col">
<small class="text-muted"><strong>ZONE</strong></small>
<h1 class="mb-3"><samp>{{conf_data['name']}}</samp></h1>
</div>
<div class="col">
<small class="text-muted"><strong>ACTION</strong></small><br>
{% if conf_data['checked'] == "checked" %}
<a href="#" id="{{conf_data['name']}}" {{conf_data['checked']}} class="switch text-primary"><i class="bi bi-toggle2-on"></i> ON</a>
{% else %}
<a href="#" id="{{conf_data['name']}}" {{conf_data['checked']}} class="switch text-secondary"><i class="bi bi-toggle2-off"></i> OFF</a>
{% endif %}
<div class="spinner-border text-primary" role="status" style="display: none; margin-top: 10px">
<span class="sr-only">Loading...</span>
</div>
</div>
<div class="w-100"></div>
<div class="col">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['status']}}<span class="dot dot-{{conf_data['status']}}"></span></h6>
</div>
<div class="col">
<small class="text-muted"><strong>CONNECTED PEERS</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['running_peer']}}</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL DATA USAGE</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][0]}} GB</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL RECEIVED</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][1]}} GB</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL SENT</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][2]}} GB</h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted">
<strong>PUBLIC KEY</strong>
<strong style="margin-left: auto!important; opacity: 0; transition: 0.2s ease-in-out" class="text-primary">CLICK TO COPY</strong>
</small>
<h6><samp class="key">{{conf_data['public_key']}}</samp></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>LISTEN PORT</strong></small>
<h6 style="text-transform: uppercase;"><samp>
{% if conf_data['listen_port'] == "" %}
N/A
{% else %}
{{conf_data['listen_port']}}
{% endif %}
</samp></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>ADDRESS</strong></small>
<h6 style="text-transform: uppercase;"><samp>{{conf_data['conf_address']}}</samp></h6>
</div>
</div>
<hr>
<div class="button-div mb-3">
<div class="row">
<div class="col-sm">
<div class="form-group">
<label for="sort_by_dropdown"><small class="text-muted">Sort Peers By</small></label>
<select class="form-control" id="sort_by_dropdown">
<option value="status" {% if sort_tag == "status" %} {{ "selected" }} {% endif %}>Status</option>
<option value="name" {% if sort_tag == "name" %} {{ "selected" }} {% endif %}>Name</option>
<option value="allowed_ip" {% if sort_tag == "allowed_ip" %} {{ "selected" }} {% endif %}>Allowed IP</option>
</select>
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label><small class="text-muted">Refresh Interval</small></label><br>
<div class="btn-group" role="group" style="width: 100%">
<button style="width: 20%" type="button" class="btn btn-outline-primary btn-group-label refresh"><i class="bi bi-arrow-repeat"></i></button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval {% if dashboard_refresh_interval == 5000 %} {{ "active" }} {% endif %}" refresh-interval="5000">5s</button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval {% if dashboard_refresh_interval == 10000 %} {{ "active" }} {% endif %}" refresh-interval="10000">10s</button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval {% if dashboard_refresh_interval == 30000 %} {{ "active" }} {% endif %}" refresh-interval="30000">30s</button>
<button style="width: 20%" type="button" class="btn btn-outline-primary update_interval {% if dashboard_refresh_interval == 60000 %} {{ "active" }} {% endif %}" refresh-interval="60000">1m</button>
</div>
</div>
</div>
<div class="col-sm">
<div class="form-group">
<label><small class="text-muted">Display Mode</small></label><br>
<div class="btn-group" role="group" style="width: 100%">
<button style="width: 20%" type="button" class="btn btn-outline-primary display_mode {% if peer_display_mode == "grid" %} {{ "active" }} {% endif %}" display-mode="grid"><i class="bi bi-grid-fill" style="font-size: 1.5rem;"></i></button>
<button style="width: 20%" type="button" class="btn btn-outline-primary display_mode {% if peer_display_mode == "list" %} {{ "active" }} {% endif %}" display-mode="list"><i class="bi bi-list" style="font-size: 1.5rem;"></i></button>
</div>
</div>
</div>
<button type="button" class="btn btn-primary add_btn" data-toggle="modal" data-target="#add_modal">
<i class="bi bi-plus-circle-fill" style=""></i> Add Peer
</button>
</div>
</div>
</div>
<div class="row peer_list">
{% if conf_data['peer_data']|length == 0 %}
<div class="col-12" style="text-align: center; margin-top: 1.5rem"><h3 class="text-muted">Oops! No peers found ‘︿’</h3></div>
{% endif %}
{% for i in conf_data['peer_data']%}
{% if peer_display_mode == "list" %}
<div class="col-12">
{% else %}
<div class="col-sm-6 col-lg-4">
{% endif %}
<div class="card mb-3 card-{{i['status']}}">
<div class="card-body">
<div class="row">
<div class="col-sm">
<h4>
{% if not i['name']%}
{{ "Untitled" }}
{% else %}
{{i['name']}}
{% endif %}
</h4>
</div>
<div class="w-100"></div>
<div class="col-6">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;" class="mb-2 h6-dot-{{i['status']}}"><span class="dot dot-{{i['status']}}" style="margin-left: 0 !important;margin-top: 5px"></span></h6>
</div>
<div class="col-6 peer_data_group" style="text-align: right">
<small class="text-muted"><strong>TRANSFER</strong></small>
<p class="text-primary" style="text-transform: uppercase; margin-bottom: 0;"><small><i class="bi bi-arrow-down-right"></i> {{i['total_receive']}} GB</small></p>
<p class="text-success" style="text-transform: uppercase; margin-bottom: 0"><small><i class="bi bi-arrow-up-right"></i> {{i['total_sent']}} GB</small></p>
</div>
<div class="col-sm">
<small class="text-muted" style="display: flex">
<strong>PEER</strong>
<strong style="margin-left: auto!important; opacity: 0; transition: 0.2s ease-in-out" class="text-primary">CLICK TO COPY</strong></small>
<h6><samp class="ml-auto key">{{i['id']}}</samp></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>ALLOWED IP</strong></small>
<h6 style="text-transform: uppercase;">{{i['allowed_ip']}}</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>LATEST HANDSHAKE</strong></small>
<h6 style="text-transform: uppercase;">{{i['latest_handshake']}}</h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted"><strong>END POINT</strong></small>
<h6 style="text-transform: uppercase;">{{i['endpoint']}}</h6>
</div>
<div class="w-100"></div>
<div class="col-sm"><hr><div class="button-group" style="display:flex"><button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-gear-fill"></i></button><button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-x-circle-fill"></i></button>
{% if i['private_key'] %}
<div class="share_peer_btn_group" style="margin-left: auto !important; display: inline">
<button type="button" class="btn btn-outline-success btn-qrcode-peer btn-control" img_src="/qrcode/{{ conf_data['name'] }}?id={{ i['id']|urlencode }}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="width: 19px;" fill="#28a745"><path d="M3 11h8V3H3v8zm2-6h4v4H5V5zM3 21h8v-8H3v8zm2-6h4v4H5v-4zM13 3v8h8V3h-8zm6 6h-4V5h4v4zM13 13h2v2h-2zM15 15h2v2h-2zM13 17h2v2h-2zM17 17h2v2h-2zM19 19h2v2h-2zM15 19h2v2h-2zM17 13h2v2h-2zM19 15h2v2h-2z"/></svg>
</button>
<a href="/download/{{ conf_data['name'] }}?id={{ i['id']|urlencode }}" class="btn btn-outline-info btn-download-peer btn-control">
<i class="bi bi-download"></i>
</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{%endfor%}
</div>
</main>

View File

@ -0,0 +1,25 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{ title }} | WireGate</title>
<link rel="manifest" href="{{ url_for('static',filename='json/manifest.json') }}">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="WireGate">
<meta name="apple-mobile-web-app-title" content="WireGate">
<meta name="msapplication-starturl" content="/">
<link rel="apple-touch-icon" sizes="192x192" href="{{ url_for('static',filename='img/192x192ios.png') }}">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="{{ url_for('static',filename='img/logo.png') }}"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='css/dashboard.min.css') }}">
<!-- THEME APPLY HERE -->
{% if session["theme"] == "dark" %}
<link rel= "stylesheet" type= "text/css" href="{{ url_for('static',filename='css/theme/dark.min.css') }}" id="darkThemeCSS">
{% endif %}
<!-- THEME APPLY HERE -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js" integrity="sha512-QSkVNOCYLtj73J4hbmVoOV6KVZuMluZlioC+trLpewV8qMjsWqlIQvkn1KGX2StWvPMdWGBqim1xlC8krl1EKQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
</head>

View File

@ -0,0 +1,77 @@
<!-- index.html - < WGDashboard > - Copyright(C) 2021 Donald Zou [https://github.com/donaldzou]-->
<html lang="en">
{% with %}
{% set title="Home" %}
{% include "header.html"%}
{% endwith %}
<body>
{% include "navbar.html" %}
<div class="container-fluid">
{% include "sidebar.html" %}
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mb-4">
<div style="display: flex; flex-direction: row; align-items: center;">
<h1 class="pb-4 mt-4">Home</h1>
</div>
<!-- {% if msg != "" %}
<div class="alert alert-danger" role="alert">
Configuration toggle failed. Please check the following error message:
</div>
<pre class="index-alert"><code>{{ msg }}</code></pre>
{% endif %} -->
<div class="index-alert alert alert-danger d-none" role="alert">
Configuration toggle failed. Please check the following error message:
</div>
<pre class="index-alert index-alert-full d-none"><code></code></pre>
{% if conf == [] %}
<p class="text-muted">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>
{% endif %}
{% for i in conf%}
<div class="card mt-3 conf_card" data-conf-id="{{i['conf']}}">
<div class="card-body">
<div class="row">
<div class="col card-col">
<small class="text-muted"><strong>ZONE</strong></small>
<a href="/configuration/{{i['conf']}}" class="conf_link">
<h6 class="card-title" style="margin:0 !important;"><samp>{{i['conf']}}</samp></h6>
</a>
</div>
<div class="col card-col">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase; margin:0 !important;"><span>{{i['status']}}</span><span class="dot dot-{{i['status']}}"></span></h6>
</div>
<div class="col-sm card-col">
<small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="margin:0 !important;"><samp>{{i['public_key']}}</samp></h6>
</div>
<div class="col-sm index-switch">
<div class="switch-test">
<input type="checkbox" class="toggle--switch" id="{{i['conf']}}-switch" {{i['checked']}} data-conf-id="{{i['conf']}}">
<label for="{{i['conf']}}-switch" class="toggleLabel"></label>
</div>
</div>
</div>
<div class="card-message"></div>
</div>
</div>
{%endfor%}
</main>
</div>
<div class="position-fixed top-0 right-0 p-3 toastContainer" style="z-index: 5; right: 0; top: 50px;"></div>
</div>
</div>
</div>
</div>
</div>
{% include "tools.html" %}
</body>
{% include "footer.html" %}
<script src="{{ url_for('static',filename='js/wireguard.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/index.min.js') }}"></script>
</html>

View File

@ -0,0 +1,10 @@
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="/">WireGate</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="progress" style="height: 3px; position: fixed; width: 100%; z-index: 10000; background-color: transparent">
<div class="progress-bar" role="progressbar" style="z-index: 10000; width: 0%"></div>
</div>

View File

@ -0,0 +1 @@
{{ qrcode(i) }}

View File

@ -0,0 +1,179 @@
<html>
{% with %}
{% set title="Settings" %}
{% include "header.html" %}
{% endwith %}
<body>
{% include "navbar.html" %}
<div class="container-fluid">
{% include "sidebar.html" %}
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="setting-container mt-4">
{% if message != "" %}
<div class="alert alert-{{ status }}" role="alert">
{{ message }}
</div>
{% endif %}
<h1 class="">Settings</h1>
<hr>
<div class="card mb-3">
<h6 class="card-header">Dashboard Theme</h6>
<div class="card-body">
<div class="row">
<div class="col-6">
<button class='btn btn-outline-primary theme-switch-btn {% if session["theme"] == "light" %} {{ "active" }} {% endif %}' data-theme="light">
<i class="bi bi-sun-fill"></i>
Light
</button>
</div>
<div class="col-6">
<button class='btn btn-outline-primary theme-switch-btn {% if session["theme"] == "dark" %} {{ "active" }} {% endif %}' data-theme="dark">
<i class="bi bi-moon-fill"></i>
Dark
</button>
</div>
</div>
</div>
</div>
<hr>
{% if required_auth == "true" %}
<div class="card mb-3">
<h6 class="card-header">Peer Default Settings</h6>
<div class="card-body">
<form action="/update_peer_default_config" method="post">
<div class="form-group">
<div class="row">
<div class="col-sm-6">
<label for="peer_global_DNS">DNS</label>
<input type="text" class="form-control mb-4" id="peer_global_DNS"
name="peer_global_DNS"
value="{{ peer_global_DNS }}" required>
</div>
<div class="col-sm-6">
<label for="peer_endpoint_allowed_ip">Peer Endpoint Allowed IPs</label>
<input type="text" class="form-control mb-4" id="peer_endpoint_allowed_ip"
name="peer_endpoint_allowed_ip"
value="{{ peer_endpoint_allowed_ip }}" required>
</div>
<div class="col-sm-6">
<label for="peer_mtu">MTU</label>
<input type="text" class="form-control mb-4" id="peer_mtu"
name="peer_mtu"
value="{{ peer_mtu }}">
</div>
<div class="col-sm-6">
<label for="peer_keep_alive">Persistent Keepalive</label>
<input type="text" class="form-control mb-4" id="peer_keep_alive"
name="peer_keep_alive"
value="{{ peer_keepalive }}">
</div>
<div class="col-sm-12">
<label for="peer_remote_endpoint"><strong>Peer Remote Endpoint (This will be change globally, and will be apply to all peer's QR code and configuration file.)</strong></label>
<input type="text" class="form-control mb-4" id="peer_remote_endpoint"
name="peer_remote_endpoint"
value="{{ peer_remote_endpoint }}" required>
</div>
</div>
<button class="btn btn-success" type="submit">Update Peer Default Settings</button>
</div>
</form>
</div>
</div>
<hr>
<div class="card mb-3">
<h6 class="card-header">WireGuard Configuration Path</h6>
<div class="card-body">
<form action="/update_wg_conf_path" method="post" class="update_wg_conf_path">
<div class="form-group">
<label for="wg_conf_path">Path</label>
<input type="text" class="form-control mb-2" id="wg_conf_path" name="wg_conf_path"
value="{{ wg_conf_path }}">
<p class="text-muted">Remember to remove <code>/</code> at the end of your path. e.g <code>/etc/wireguard</code>
</p>
<button class="btn btn-danger change_path">Update Path & Restart Dashboard</button>
</div>
</form>
</div>
</div>
<div class="card mb-3">
<h6 class="card-header">Account</h6>
<div class="card-body">
<form action="/update_acct" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control mb-4" id="username" name="username"
value="{{ session['username'] }}" required>
<button type="submit" class="btn btn-danger">Update Account</button>
</div>
</form>
</div>
</div>
<div class="card mb-3">
<h6 class="card-header">Security</h6>
<div class="card-body">
<form action="/update_pwd" method="post">
<div class="form-group">
<label for="currentpass">Current Password</label>
<input type="password" class="form-control mb-2" id="currentpass" name="currentpass">
<label for="newpass">New Password</label>
<input type="password" class="form-control mb-2" id="newpass" name="newpass">
<label for="repnewpass">Repeat New Password</label>
<input type="password" class="form-control mb-4" id="repnewpass" name="repnewpass">
<button type="submit" class="btn btn-danger">Update Password</button>
</div>
</form>
</div>
</div>
{% endif %}
<div class="card">
<h6 class="card-header">Dashboard Configuration</h6>
<div class="card-body">
<form action="/update_app_ip_port" method="post" class="update_app_ip_port">
<div class="form-group">
<div class="row">
<div class="col-sm">
<label for="app_ip">Dashboard IP</label>
<input type="text" class="form-control mb-2" id="app_ip" name="app_ip" value="{{ app_ip }}">
<p><small class="text-danger mb-4">0.0.0.0 means it can be access by anyone with your server
IP Address.</small></p>
</div>
</div>
<button type="button" class="btn btn-danger confirm_modal" data-toggle="modal"
data-target="#confirmModal">Update Configuration & Restart Dashboard
</button>
</div>
</form>
</div>
</div>
</div>
</main>
<!-- Modal -->
<div class="modal fade" id="confirmModal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Confirm Dashboard Configuration</h5>
</div>
<div class="modal-body">
<small>Dashboard Original IP</small>
<p>{{ app_ip }}</p>
<small style="font-weight: bold" class="text-bold">Dashboard New IP</small>
<p class="app_new_ip text-bold text-danger" style="font-weight: bold"></p>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel_restart" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger confirm_restart">Confirm & Restart Dashboard</button>
</div>
</div>
</div>
</div>
</div>
{% include "tools.html" %}
<div class="position-fixed top-0 right-0 p-3 toastContainer" style="z-index: 5; right: 0; top: 50px;"></div>
</body>
{% include "footer.html" %}
<script src="{{ url_for('static',filename='js/settings.js') }}"></script>
</html>

View File

@ -0,0 +1,66 @@
<div class="row">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link sb-home-url" href="/">Home</a></li>
{% if "username" in session %}
<li class="nav-item"><a class="nav-link sb-settings-url" href="/settings">Settings</a></li>
{% endif %}
{% if session['update'] == "true" %}
<li class="nav-item sb-update-li">
<a class="nav-link sb-update-url" href="https://github.com/donaldzou/WGDashboard#-how-to-update-the-dashboard">New Update Available!<span class="dot dot-running"></span></a>
</li>
{% endif %}
</ul>
<hr>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Zones</span>
</h6>
<ul class="nav flex-column">
{% for i in conf%}
<li class="nav-item"><a class="nav-link nav-conf-link sb-{{i['conf']}}-url" href="/configuration/{{i['conf']}}" data-conf-id="{{i['conf']}}"><samp>{{i['conf']}}</samp></a></li>
{%endfor%}
</ul>
<hr>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Tools</span>
</h6>
<ul class="nav flex-column">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#ping_modal" href="#">Ping</a></li>
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#traceroute_modal" href="#">Traceroute</a></li>
</ul>
</ul>
<hr>
{% if "username" in session %}
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link text-danger" href="/signout" style="font-weight: bold">Sign Out</a></li>
</ul>
{% endif %}
<ul class="nav flex-column">
<li class="nav-item"><a href="https://hub.docker.com/repository/docker/noxcis/wg-dashboard/general"><small class="nav-link text-muted">{{ session['dashboard_version'] }}</small></a></li>
</ul>
</div>
</nav>
</div>
</div>
<script
src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
// Add click event listener to links in the "Zones" section
$('.nav-conf-link').on('click', function(event) {
event.preventDefault(); // Prevent default behavior (e.g., navigating to the href)
// Get the href attribute of the clicked link
var url = $(this).attr('href');
// Redirect to the specified URL
window.location.href = url;
});
});
</script

View File

@ -0,0 +1,87 @@
<html>
{% with title="Sign In"%}
{% include "header.html"%}
{% endwith %}
<style>
.login-container-fluid{
display: flex;
height: calc( 100% - 240px );
align-items: center;
}
</style>
<body>
{% include "navbar.html" %}
<div class="container-fluid login-container-fluid">
<main role="main" class="container login-container">
<div class="login-box" style="margin: auto !important;">
<h1 class="text-center">Sign in</h1>
<h5 class="text-center">to WireGate</h5>
<form style="margin-left: auto !important; margin-right: auto !important; max-width: 500px;" action="/auth" method="post">
{% if message != "" %}
<div class="alert alert-warning" role="alert">You need to sign in first</div>
{% endif %}
<div class="alert alert-danger d-none" role="alert" style="margin-top: 1rem; margin-bottom: 0rem;"></div>
<div class="form-group">
<label for="username" class="text-left" style="font-size: 1rem"><i class="bi bi-person-circle"></i></label>
<input type="text" class="form-control" id="username" name="username" placeholder="Your username" required>
</div>
<div class="form-group">
<label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label>
<input type="password" class="form-control" id="password" name="password" placeholder="Your password" required>
</div>
<button type="submit" class="btn btn-dark" style="width: 100%">Sign In</button>
</form>
</div>
</main>
</div>
<small class="text-muted" style="position: fixed; bottom: 0; width: 100%; text-align: center; margin-bottom: 2rem">Version: {{ version }}</small>
</body>
{% include "footer.html" %}
<script>
let loginButton = $('button[type="submit"]');
loginButton.on("click", function(e){
e.preventDefault();
let $password = $("#password");
let $username = $("#username");
let req = [$password, $username];
let check = true
for (let i = 0; i < req.length; i++){
if ($(req[i]).val().length === 0){
loginButton.html("Sign In");
check = false;
$(req[i]).addClass("is-invalid");
break;
}
}
if (check){
$(this).html("Signing In...").attr("disabled", "disabled");
$.ajax({
url: "/auth",
method: "POST",
headers:{"Content-Type": "application/json"},
data: JSON.stringify({
"username": $("#username").val(),
"password": $("#password").val()
})
}).done(function(res){
if (res.status === true){
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("redirect")){
if (document.URL.substring(0, 5) == "http:"){
window.location.replace(`http://${urlParams.get("redirect")}`)
}else if (document.URL.substring(0, 5) == "https"){
window.location.replace(`https://${urlParams.get("redirect")}`)
}
}else{
window.location.replace("/");
}
}else{
$(".alert").html(res.msg).removeClass("d-none").fadeIn();
loginButton.html("Sign In").removeAttr("disabled");
$("input[required]").addClass("is-invalid");
}
});
}
});
</script>
</html>

View File

@ -0,0 +1,121 @@
<div class="modal fade" id="ping_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Ping</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm">
<div class="mb-3">
<small>Configuration</small>
<select class="form-control mt-2 conf_dropdown">
<option value="none" selected="selected" disabled>Select Configuration</option>
{% for i in conf%}
<option value="{{ i['conf'] }}">{{ i['conf'] }}</option>
{%endfor%}
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>IP</small>
<select class="form-control mt-2 ip_dropdown">
<option value="none" selected="selected" disabled>Choose an IP</option>
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>Ping Count</small>
<input type="number" class="form-control mt-2 ping_count" min=1 value=4>
</div>
</div>
</div>
<hr>
<div class="ping_result">
<table class="table">
<tbody></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary send_ping" disabled>Ping</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="traceroute_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Traceroute</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm">
<div class="mb-3">
<small>Configuration</small>
<select class="form-control mt-2 conf_dropdown">
<option value="none" selected="selected" disabled>Select Configuration</option>
{% for i in conf%}
<option value="{{ i['conf'] }}">{{ i['conf'] }}</option>
{%endfor%}
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>IP</small>
<select class="form-control mt-2 ip_dropdown">
<option value="none" selected="selected" disabled>Choose an IP</option>
</select>
</div>
</div>
</div>
<button class="btn btn-primary send_traceroute" disabled>Traceroute</button>
<hr>
<div class="traceroute_result">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Hop</th>
<th scope="col">IP</th>
<th scope="col">Avg RTT</th>
<th scope="col">Min RTT</th>
<th scope="col">Max RTT</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="update_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">How to update dashboard</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<pre><code>$ sudo sh wgd.sh stop</code><br><code>$ sudo sh wgd.sh update</code><br><code>$ sudo sh wgd.sh start</code></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

113
WG-Dash/src/util.py Normal file
View File

@ -0,0 +1,113 @@
import re
import subprocess
import dashboard
"""
Helper Functions
"""
# Regex Match
def regex_match(regex, text):
pattern = re.compile(regex)
return pattern.search(text) is not None
# Check IP format
def check_IP(ip):
ip_patterns = (
r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}",
r"[0-9a-fA-F]{0,4}(:([0-9a-fA-F]{0,4})){1,7}$"
)
for match_pattern in ip_patterns:
match_result = regex_match(match_pattern, ip)
if match_result:
result = match_result
break
else:
result = None
return result
# Clean IP
def clean_IP(ip):
return ip.replace(' ', '')
# Clean IP with range
def clean_IP_with_range(ip):
return clean_IP(ip).split(',')
# Check IP with range
def check_IP_with_range(ip):
ip_patterns = (
r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|\/)){4}([0-9]{1,2})(,|$)",
r"[0-9a-fA-F]{0,4}(:([0-9a-fA-F]{0,4})){1,7}\/([0-9]{1,3})(,|$)"
)
for match_pattern in ip_patterns:
match_result = regex_match(match_pattern, ip)
if match_result:
result = match_result
break
else:
result = None
return result
# Check allowed ips list
def check_Allowed_IPs(ip):
ip = clean_IP_with_range(ip)
for i in ip:
if not check_IP_with_range(i): return False
return True
# Check DNS
def check_DNS(dns):
dns = dns.replace(' ', '').split(',')
status = True
for i in dns:
if not (check_IP(i) or regex_match("(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", i)):
return False
return True
# Check remote endpoint
def check_remote_endpoint(address):
return (check_IP(address) or regex_match("(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]",
address))
def deletePeers(config_name, delete_keys, cur, db):
sql_command = []
wg_command = ["wg", "set", config_name]
for delete_key in delete_keys:
if delete_key not in dashboard.get_conf_peer_key(config_name):
return "This key does not exist"
sql_command.append("DELETE FROM " + config_name + " WHERE id = '" + delete_key + "';")
wg_command.append("peer")
wg_command.append(delete_key)
wg_command.append("remove")
try:
print("deleting...")
remove_wg = subprocess.check_output(" ".join(wg_command),
shell=True, stderr=subprocess.STDOUT)
save_wg = subprocess.check_output(f"wg-quick save {config_name}", shell=True, stderr=subprocess.STDOUT)
cur.executescript(' '.join(sql_command))
db.commit()
except subprocess.CalledProcessError as exc:
return exc.output.strip()
return "true"
def checkJSONAllParameter(required, data):
if len(data) == 0:
return False
for i in required:
if i not in list(data.keys()) or len(data[i]) == 0:
return False
return True

220
WG-Dash/src/wgd.sh Normal file
View File

@ -0,0 +1,220 @@
#!/bin/bash
start_wgd () {
echo -e "Start Dashboard--------------------------------------------------------------------------------\n"
echo ""
echo ""
uwsgi --ini wiregate.ini
echo "--------------------------------------------------------------------------------"
}
newconf_wgd () {
newconf_wgd0
newconf_wgd1
newconf_wgd2
newconf_wgd3
newconf_wgd4
newconf_wgd5
}
newconf_wgd0() {
local port_wg0=$WG_DASH_PORT_RANGE_STARTPORT
private_key=$(wg genkey)
public_key=$(echo "$private_key" | wg pubkey)
cat <<EOF >"/etc/wireguard/ADMINS.conf"
[Interface]
PrivateKey = $private_key
Address = 10.0.0.1/24
ListenPort = $port_wg0
SaveConfig = true
PostUp = /home/app/FIREWALLS/Admins/wg0-nat.sh
PreDown = /home/app/FIREWALLS/Admins/wg0-dwn.sh
EOF
if [ ! -f "/master-key/master.conf" ]; then
make_master_config # Only call make_master_config if master.conf doesn't exist
fi
}
newconf_wgd1() {
local port_wg1=$WG_DASH_PORT_RANGE_STARTPORT
local port_wg1=$((port_wg1 + 1))
private_key=$(wg genkey)
public_key=$(echo "$private_key" | wg pubkey)
cat <<EOF >"/etc/wireguard/MEMEBERS.conf"
[Interface]
PrivateKey = $private_key
Address = 192.168.10.1/24
ListenPort = $port_wg1
SaveConfig = true
PostUp = /home/app/FIREWALLS/Members/wg1-nat.sh
PreDown = /home/app/FIREWALLS/Members/wg1-dwn.sh
EOF
}
newconf_wgd2() {
local port_wg2=$WG_DASH_PORT_RANGE_STARTPORT
local port_wg2=$((port_wg2 + 2))
private_key=$(wg genkey)
public_key=$(echo "$private_key" | wg pubkey)
cat <<EOF >"/etc/wireguard/LANP2P.conf"
[Interface]
PrivateKey = $private_key
Address = 172.16.0.1/24
ListenPort = $port_wg2
SaveConfig = true
PostUp = /home/app/FIREWALLS/LAN-only-users/wg2-nat.sh
PreDown = /home/app/FIREWALLS/LAN-only-users/wg2-dwn.sh
EOF
}
newconf_wgd3() {
local port_wg3=$WG_DASH_PORT_RANGE_STARTPORT
local port_wg3=$((port_wg3 + 3))
private_key=$(wg genkey)
public_key=$(echo "$private_key" | wg pubkey)
cat <<EOF >"/etc/wireguard/GUESTS.conf"
[Interface]
PrivateKey = $private_key
Address = 192.168.20.1/24
ListenPort = $port_wg3
SaveConfig = true
PostUp = /home/app/FIREWALLS/Guest/wg3-nat.sh
PreDown = /home/app/FIREWALLS/Guest/wg3-dwn.sh
EOF
}
newconf_wgd4() {
local port_wg4=$WG_DASH_PORT_RANGE_STARTPORT
local port_wg4=$((port_wg4 + 4))
private_key=$(wg genkey)
public_key=$(echo "$private_key" | wg pubkey)
cat <<EOF >"/etc/wireguard/IPV6ADMINS.conf"
[Interface]
PrivateKey = $private_key
Address = 192.168.30.1/24
Address = 2001:db8:30:1::/64
ListenPort = $port_wg4
SaveConfig = true
PostUp = /home/app/FIREWALLS/IPV6/wg0-nat.sh
PreDown = /home/app/FIREWALLS/IPV6/wg0-dwn.sh
EOF
}
newconf_wgd5() {
local port_wg5=$WG_DASH_PORT_RANGE_STARTPORT
local port_wg5=$((port_wg5 + 5))
private_key=$(wg genkey)
public_key=$(echo "$private_key" | wg pubkey)
cat <<EOF >"/etc/wireguard/IPV6MEMBERS.conf"
[Interface]
PrivateKey = $private_key
Address = 192.168.40.1/24
Address = 2001:db8:40:1::/64
ListenPort = $port_wg5
SaveConfig = true
PostUp = /home/app/FIREWALLS/IPV6/wg1-nat.sh
PreDown = /home/app/FIREWALLS/IPV6/wg1-dwn.sh
EOF
}
make_master_config() {
local svr_config="/etc/wireguard/ADMINS.conf"
# Check if the specified config file exists
if [ ! -f "$svr_config" ]; then
echo "Error: Config file $svr_config not found."
exit 1
fi
#Function to generate a new peer's public key
generate_public_key() {
local private_key="$1"
echo "$private_key" | wg pubkey
}
# Function to generate a new preshared key
generate_preshared_key() {
wg genpsk
}
# Generate the new peer's public key, preshared key, and allowed IP
wg_private_key=$(wg genkey)
peer_public_key=$(generate_public_key "$wg_private_key")
preshared_key=$(generate_preshared_key)
# Add the peer to the WireGuard config file with the preshared key
echo -e "\n[Peer]" >> "$svr_config"
echo "PublicKey = $peer_public_key" >> "$svr_config"
echo "PresharedKey = $preshared_key" >> "$svr_config"
echo "AllowedIPs = 10.0.0.254/32" >> "$svr_config"
server_public_key=$(grep -E '^PrivateKey' "$svr_config" | awk '{print $NF}')
svrpublic_key=$(echo "$server_public_key" | wg pubkey)
# Generate the client config file
cat <<EOF >"/home/app/master-key/master.conf"
[Interface]
PrivateKey = $wg_private_key
Address = 10.0.0.254/32
DNS = 10.2.0.100,10.2.0.100
MTU = 1420
[Peer]
PublicKey = $svrpublic_key
AllowedIPs = 0.0.0.0/0
Endpoint = $WG_DASH_SERVER_IP:$WG_DASH_PORT_RANGE_STARTPORT
PersistentKeepalive = 21
PresharedKey = $preshared_key
EOF
}
if [ "$#" != 1 ];
then
help
elif [ "$1" = "install" ]; then
install_wgd
elif [ "$1" = "debug" ]; then
start_wgd_debug
elif [ "$1" = "start" ]; then
start_wgd
elif [ "$1" = "newconfig" ]; then
newconf_wgd
else
help
fi

44
WG-Dash/src/wiregate.ini Normal file
View File

@ -0,0 +1,44 @@
[uwsgi]
plugin = python3
module = dashboard:app
pythonpath = /usr
socket = :10086
chmod-socket = 660
master = true
single-interpreter = true
vacuum = true
die-on-term = true
need-app = true
enable-threads = true
processes = 2 # Set to 1 to limit the number of processes
threads = 4 # Adjust based on the CPU and application requirements
workers = 5 # Adjust based on the CPU and application requirements
disable-logging = false
log-4xx = true
log-5xx = true
max-requests = 500 # Adjust based on the available memory
max-worker-lifetime = 1800 # Adjust based on the available memory
reload-on-rss = 1024 # Adjust based on the available memory
worker-reload-mercy = 5
buffer-size = 6000 # Adjust based on response sizes
cheaper = 2
cheaper-initial = 2 # Adjust based on the available memory
cheaper-overload = 30
cheaper-step = 2
cheaper-busyness-multiplier = 30
cheaper-busyness-min = 20
cheaper-busyness-max = 70
cheaper-busyness-backlog-alert = 8
cheaper-busyness-backlog-step = 2
#logto = /dev/null

View File

@ -2,14 +2,11 @@
api_host=$API_HOST
if [[ "$HEROKU_APP_NAME" =~ "-pr-" ]]
then
api_host=""
fi
echo "building client..."
cd client
yarn --production=false
yarn --production=true
yarn build
cd ../

View File

@ -2,9 +2,9 @@
TZ=UTC
VITE_API_HOST=localhost
VITE_API_PROTOCOL=http
VITE_API_PORT=3001
VITE_API_PORT=80
VITE_COMMIT_SHA=some_sha
MODE=production
# To display darkwire version
VITE_COMMIT_SHA=some_sha

View File

@ -5,19 +5,19 @@ let port;
switch (import.meta.env.MODE) {
case 'staging':
host = import.meta.env.VITE_API_HOST;
protocol = import.meta.env.VITE_API_PROTOCOL || 'https';
port = import.meta.env.VITE_API_PORT || 443;
host = import.meta.env.VITE_API_HOST || 'localhost';
protocol = import.meta.env.VITE_API_PROTOCOL || 'http';
port = import.meta.env.VITE_API_PORT || 80;
break;
case 'production':
host = import.meta.env.VITE_API_HOST;
protocol = import.meta.env.VITE_API_PROTOCOL || 'https';
port = import.meta.env.VITE_API_PORT || 443;
host = import.meta.env.VITE_API_HOST || 'localhost';
protocol = import.meta.env.VITE_API_PROTOCOL || 'http';
port = import.meta.env.VITE_API_PORT || 80;
break;
default:
host = import.meta.env.VITE_API_HOST || 'localhost';
protocol = import.meta.env.VITE_API_PROTOCOL || 'http';
port = import.meta.env.VITE_API_PORT || 3001;
port = import.meta.env.VITE_API_PORT || 80;
}
export default {

73
dev-docker-compose.yml Normal file
View File

@ -0,0 +1,73 @@
version: "3"
networks:
private_network:
ipam:
driver: default
config:
- subnet: 10.2.0.0/24
services:
darkwire.io:
build:
context: .
environment:
- TZ=UTC
- VITE_API_PORT=4242
- VITE_API_HOST=127.0.0.1
- VITE_API_PROTOCOL=http
- VITE_COMMIT_SHA=some_sha
- VITE_MAX_FILE_SIZE=4
- MAILGUN_API_KEY=api-key
- MAILGUN_DOMAIN=darkwire.io
- ABUSE_TO_EMAIL_ADDRESS=abuse@darkwire.io
- ABUSE_FROM_EMAIL_ADDRESS=Darkwire <no-reply@darkwire.io>
- CLIENT_DIST_DIRECTORY='client/dist'
- ROOM_HASH_SECRET='some-uuid'
- SITE_URL=https://darkwire.io
- STORE_BACKEND=memory
#- STORE_HOST=$STORE_HOST
ports:
- 80:4242
networks:
private_network:
ipv4_address: 10.2.0.4
wiregate:
build: ./WG-Dash
#image: noxcis/wg-dashboard:kraken
container_name: wiregate
cap_add:
- NET_ADMIN
- SYS_MODULE
restart: unless-stopped
volumes:
- ./Global-Configs/Wiregate-Database:/home/app/db
- ./Global-Configs/Master-Key:/home/app/master-key
environment:
- WG_DASH_USER=admin
- WG_DASH_PASS=admin
- WG_DASH_SECRET_KEY=some-super-secret_key
- WG_DASH_SERVER_IP=0.0.0.0
- WG_DASH_DNS=1.1.1.1, 8.8.8.8
- WG_DASH_PEER_ENDPOINT_ALLOWED_IP=0.0.0.0/0
- WG_DASH_KEEP_ALIVE=21
- WG_DASH_MTU=1420
- WG_DASH_PORT_RANGE_STARTPORT=443
ports:
- "443-448:443-448/udp"
- 8000:80/tcp
sysctls:
- net.ipv4.ip_forward=1
- net.ipv6.conf.all.forwarding=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
networks:
private_network:
ipv4_address: 10.2.0.3

View File

@ -6,7 +6,7 @@ services:
environment:
- TZ=UTC
- VITE_API_PORT=80
- VITE_API_HOST=localhost
- VITE_API_HOST=127.0.0.1
- VITE_API_PROTOCOL=http
- VITE_COMMIT_SHA=some_sha
- VITE_MAX_FILE_SIZE=4

View File

@ -17,7 +17,7 @@ import getStore from './store/index.js';
dotenv.config();
const env = process.env.NODE_ENV || 'development';
const env = process.env.NODE_ENV || 'production';
const app = new Koa();
const PORT = process.env.VITE_API_PORT;
@ -31,10 +31,10 @@ const siteURL = process.env.SITE_URL;
const store = getStore();
if ((siteURL || env === 'development') && !isReviewApp) {
if ((siteURL || env === 'production') && !isReviewApp) {
app.use(
cors({
origin: env === 'development' ? '*' : siteURL,
origin: env === 'production' ? '*' : siteURL,
allowMethods: ['GET', 'HEAD', 'POST'],
credentials: true,
}),
@ -66,7 +66,7 @@ router.post('/abuse/:roomId', koaBody, async ctx => {
app.use(router.routes());
const apiHost = process.env.API_HOST;
const cspDefaultSrc = `'self'${apiHost ? ` https://${apiHost} wss://${apiHost}` : ''}`;
const cspDefaultSrc = `'self'${apiHost ? ` http://${apiHost} wss://${apiHost}` : ''}`;
function setStaticFileHeaders(ctx) {
ctx.set({
@ -99,7 +99,7 @@ if (clientDistDirectory) {
});
}
const protocol = (process.env.PROTOCOL || 'http') === 'http' ? http : https;
const protocol = (process.env.PROTOCOL || 'http') === 'http' ? http : http;
const httpServer = protocol.createServer(app.callback());

View File

@ -4,6 +4,11 @@
set_env() {
set -e
echo "
" > /etc/nginx/http.d/default.conf
echo "
TZ=UTC
@ -11,6 +16,7 @@ VITE_API_HOST=$VITE_API_HOST
VITE_API_PROTOCOL=$VITE_API_PROTOCOL
VITE_API_PORT=$VITE_API_PORT
VITE_COMMIT_SHA=$VITE_COMMIT_SHA
MODE=production
# To display darkwire version
VITE_COMMIT_SHA=$VITE_COMMIT_SHA