mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2024-11-22 23:27:45 +01:00
Started to refactor dashboard.py with dashboard_new.py and trying really hard to figure out sqlalchemy lol
This commit is contained in:
parent
f671c992e1
commit
864f82ba11
@ -288,9 +288,6 @@ def get_transfer(config_name):
|
|||||||
# {round(cumulative_sent + cumulative_receive, 4)}, '{now_string}')
|
# {round(cumulative_sent + cumulative_receive, 4)}, '{now_string}')
|
||||||
# ''')
|
# ''')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_endpoint(config_name):
|
def get_endpoint(config_name):
|
||||||
"""
|
"""
|
||||||
Get endpoint from all peers of a configuration
|
Get endpoint from all peers of a configuration
|
||||||
@ -310,8 +307,6 @@ def get_endpoint(config_name):
|
|||||||
% (data_usage[count + 1], data_usage[count]))
|
% (data_usage[count + 1], data_usage[count]))
|
||||||
count += 2
|
count += 2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_allowed_ip(conf_peer_data, config_name):
|
def get_allowed_ip(conf_peer_data, config_name):
|
||||||
"""
|
"""
|
||||||
Get allowed ips from all peers of a configuration
|
Get allowed ips from all peers of a configuration
|
||||||
@ -324,8 +319,6 @@ def get_allowed_ip(conf_peer_data, config_name):
|
|||||||
g.cur.execute("UPDATE " + config_name + " SET allowed_ip = '%s' WHERE id = '%s'"
|
g.cur.execute("UPDATE " + config_name + " SET allowed_ip = '%s' WHERE id = '%s'"
|
||||||
% (i.get('AllowedIPs', '(None)'), i["PublicKey"]))
|
% (i.get('AllowedIPs', '(None)'), i["PublicKey"]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_peers_data(config_name):
|
def get_all_peers_data(config_name):
|
||||||
"""
|
"""
|
||||||
Look for new peers from WireGuard
|
Look for new peers from WireGuard
|
||||||
@ -488,7 +481,6 @@ def get_conf_total_data(config_name):
|
|||||||
download_total = round(download_total, 4)
|
download_total = round(download_total, 4)
|
||||||
return [total, upload_total, download_total]
|
return [total, upload_total, download_total]
|
||||||
|
|
||||||
|
|
||||||
def get_conf_status(config_name):
|
def get_conf_status(config_name):
|
||||||
"""
|
"""
|
||||||
Check if the configuration is running or not
|
Check if the configuration is running or not
|
||||||
@ -690,9 +682,8 @@ def auth_req():
|
|||||||
@return: Redirect
|
@return: Redirect
|
||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
if getattr(g, 'db', None) is None:
|
if getattr(g, 'db', None) is None:
|
||||||
|
print('hi')
|
||||||
g.db = connect_db()
|
g.db = connect_db()
|
||||||
g.cur = g.db.cursor()
|
g.cur = g.db.cursor()
|
||||||
conf = get_dashboard_conf()
|
conf = get_dashboard_conf()
|
||||||
@ -781,6 +772,7 @@ def auth():
|
|||||||
return jsonify({"status": False, "msg": "Username or Password is incorrect."})
|
return jsonify({"status": False, "msg": "Username or Password is incorrect."})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Index Page
|
Index Page
|
||||||
"""
|
"""
|
||||||
@ -796,8 +788,8 @@ def index():
|
|||||||
if "switch_msg" in session:
|
if "switch_msg" in session:
|
||||||
msg = session["switch_msg"]
|
msg = session["switch_msg"]
|
||||||
session.pop("switch_msg")
|
session.pop("switch_msg")
|
||||||
return render_template('index_new.html')
|
# return render_template('index_new.html')
|
||||||
# return render_template('index.html', conf=get_conf_list(), msg=msg)
|
return render_template('index.html', conf=get_conf_list(), msg=msg)
|
||||||
|
|
||||||
|
|
||||||
# Setting Page
|
# Setting Page
|
||||||
@ -1788,6 +1780,37 @@ def traceroute_ip():
|
|||||||
except Exception:
|
except Exception:
|
||||||
return "Error"
|
return "Error"
|
||||||
|
|
||||||
|
### NEW API ROUTES
|
||||||
|
|
||||||
|
@app.route('/api/authenticate', methods=['POST'])
|
||||||
|
def api_auth():
|
||||||
|
"""
|
||||||
|
Authentication request
|
||||||
|
@return: json object indicating verifying
|
||||||
|
"""
|
||||||
|
data = request.get_json()
|
||||||
|
config = get_dashboard_conf()
|
||||||
|
password = hashlib.sha256(data['password'].encode())
|
||||||
|
if password.hexdigest() == config["Account"]["password"] \
|
||||||
|
and data['username'] == config["Account"]["username"]:
|
||||||
|
session['username'] = data['username']
|
||||||
|
resp = jsonify(ResponseObject(True))
|
||||||
|
resp.set_cookie("authToken", hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest())
|
||||||
|
session.permanent = True
|
||||||
|
config.clear()
|
||||||
|
return resp
|
||||||
|
|
||||||
|
config.clear()
|
||||||
|
return jsonify(ResponseObject(False, "Username or password in incorrect."))
|
||||||
|
|
||||||
|
|
||||||
|
def ResponseObject(status = True, message = None, data = None) -> dict:
|
||||||
|
return {
|
||||||
|
"status": status,
|
||||||
|
"message": message,
|
||||||
|
"data": data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
@atexit.register
|
@atexit.register
|
||||||
@ -1911,8 +1934,6 @@ def init_dashboard():
|
|||||||
"""
|
"""
|
||||||
Create dashboard default configuration.
|
Create dashboard default configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# Set Default INI File
|
# Set Default INI File
|
||||||
if not os.path.isfile(DASHBOARD_CONF):
|
if not os.path.isfile(DASHBOARD_CONF):
|
||||||
open(DASHBOARD_CONF, "w+").close()
|
open(DASHBOARD_CONF, "w+").close()
|
||||||
|
197
src/dashboard_new.py
Normal file
197
src/dashboard_new.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
from crypt import methods
|
||||||
|
import sqlite3
|
||||||
|
import configparser
|
||||||
|
import hashlib
|
||||||
|
import ipaddress
|
||||||
|
import json
|
||||||
|
# Python Built-in Library
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from operator import itemgetter
|
||||||
|
# PIP installed library
|
||||||
|
import ifcfg
|
||||||
|
from flask import Flask, request, render_template, redirect, url_for, session, jsonify, g
|
||||||
|
from flask_qrcode import QRcode
|
||||||
|
from icmplib import ping, traceroute
|
||||||
|
|
||||||
|
# Import other python files
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from sqlalchemy.orm import mapped_column, declarative_base, Session
|
||||||
|
from sqlalchemy import FLOAT, INT, VARCHAR, select, MetaData
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
|
DASHBOARD_VERSION = 'v3.1'
|
||||||
|
CONFIGURATION_PATH = os.getenv('CONFIGURATION_PATH', '.')
|
||||||
|
DB_PATH = os.path.join(CONFIGURATION_PATH, 'db')
|
||||||
|
if not os.path.isdir(DB_PATH):
|
||||||
|
os.mkdir(DB_PATH)
|
||||||
|
DASHBOARD_CONF = os.path.join(CONFIGURATION_PATH, 'wg-dashboard.ini')
|
||||||
|
|
||||||
|
# WireGuard's configuration path
|
||||||
|
WG_CONF_PATH = None
|
||||||
|
# Dashboard Config Name
|
||||||
|
# Upgrade Required
|
||||||
|
UPDATE = None
|
||||||
|
# Flask App Configuration
|
||||||
|
app = Flask("WGDashboard")
|
||||||
|
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 5206928
|
||||||
|
app.secret_key = secrets.token_urlsafe(32)
|
||||||
|
# Enable QR Code Generator
|
||||||
|
QRcode(app)
|
||||||
|
|
||||||
|
'''
|
||||||
|
Classes
|
||||||
|
'''
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class DashboardConfig:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__config = configparser.ConfigParser(strict=False)
|
||||||
|
self.__config.read(DASHBOARD_CONF)
|
||||||
|
self.__default = {
|
||||||
|
"Account": {
|
||||||
|
"username": "admin",
|
||||||
|
"password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918"
|
||||||
|
},
|
||||||
|
"Server": {
|
||||||
|
"wg_conf_path": "/etc/wireguard",
|
||||||
|
"app_ip": "0.0.0.0",
|
||||||
|
"app_port": "10086",
|
||||||
|
"auth_req": "true",
|
||||||
|
"version": DASHBOARD_VERSION,
|
||||||
|
"dashboard_refresh_interval": "60000",
|
||||||
|
"dashboard_sort": "status",
|
||||||
|
"dashboard_theme": "light"
|
||||||
|
},
|
||||||
|
"Peers": {
|
||||||
|
"peer_global_DNS": "1.1.1.1",
|
||||||
|
"peer_endpoint_allowed_ip": "0.0.0.0/0",
|
||||||
|
"peer_display_mode": "grid",
|
||||||
|
"remote_endpoint": ifcfg.default_interface()['inet'],
|
||||||
|
"peer_MTU": "1420",
|
||||||
|
"peer_keep_alive": "21"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for section, keys in self.__default.items():
|
||||||
|
for key, value in keys.items():
|
||||||
|
exist, currentData = self.GetConfig(section, key)
|
||||||
|
if not exist:
|
||||||
|
self.SetConfig(section, key, value)
|
||||||
|
|
||||||
|
def SetConfig(self, section: str, key: str, value: any) -> bool:
|
||||||
|
if section not in self.__config:
|
||||||
|
self.__config[section] = {}
|
||||||
|
self.__config[section][key] = value
|
||||||
|
return self.SaveConfig()
|
||||||
|
|
||||||
|
def SaveConfig(self) -> bool:
|
||||||
|
try:
|
||||||
|
with open(DASHBOARD_CONF, "w+", encoding='utf-8') as configFile:
|
||||||
|
self.__config.write(configFile)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def GetConfig(self, section, key) -> [any, bool]:
|
||||||
|
if section not in self.__config:
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
if key not in self.__config[section]:
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
return True, self.__config[section][key]
|
||||||
|
|
||||||
|
|
||||||
|
def ResponseObject(status=True, message=None, data=None) -> dict:
|
||||||
|
return {
|
||||||
|
"status": status,
|
||||||
|
"message": message,
|
||||||
|
"data": data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DashboardConfig = DashboardConfig()
|
||||||
|
|
||||||
|
'''
|
||||||
|
Private Functions
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def _createPeerModel(wgConfigName):
|
||||||
|
class Peer(Base):
|
||||||
|
__tablename__ = wgConfigName
|
||||||
|
id = mapped_column(VARCHAR, primary_key=True)
|
||||||
|
private_key = mapped_column(VARCHAR)
|
||||||
|
DNS = mapped_column(VARCHAR)
|
||||||
|
endpoint_allowed_ip = mapped_column(VARCHAR)
|
||||||
|
name = mapped_column(VARCHAR)
|
||||||
|
total_receive = mapped_column(FLOAT)
|
||||||
|
total_sent = mapped_column(FLOAT)
|
||||||
|
total_data = mapped_column(FLOAT)
|
||||||
|
endpoint = mapped_column(VARCHAR)
|
||||||
|
status = mapped_column(VARCHAR)
|
||||||
|
latest_handshake = mapped_column(VARCHAR)
|
||||||
|
allowed_ip = mapped_column(VARCHAR)
|
||||||
|
cumu_receive = mapped_column(FLOAT)
|
||||||
|
cumu_sent = mapped_column(FLOAT)
|
||||||
|
cumu_data = mapped_column(FLOAT)
|
||||||
|
mtu = mapped_column(INT)
|
||||||
|
keepalive = mapped_column(INT)
|
||||||
|
remote_endpoint = mapped_column(VARCHAR)
|
||||||
|
preshared_key = mapped_column(VARCHAR)
|
||||||
|
|
||||||
|
return Peer
|
||||||
|
|
||||||
|
|
||||||
|
def _regexMatch(regex, text):
|
||||||
|
pattern = re.compile(regex)
|
||||||
|
return pattern.search(text) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _getConfigurationList():
|
||||||
|
conf = []
|
||||||
|
for i in os.listdir(WG_CONF_PATH):
|
||||||
|
if _regexMatch("^(.{1,}).(conf)$", i):
|
||||||
|
i = i.replace('.conf', '')
|
||||||
|
_createPeerModel(i).__table__.create(engine)
|
||||||
|
_createPeerModel(i + "_restrict_access").__table__.create(engine)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
API Routes
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/authenticate', methods=['POST'])
|
||||||
|
def API_AuthenticateLogin():
|
||||||
|
data = request.get_json()
|
||||||
|
password = hashlib.sha256(data['password'].encode())
|
||||||
|
print()
|
||||||
|
if password.hexdigest() == DashboardConfig.GetConfig("Account", "password")[1] \
|
||||||
|
and data['username'] == DashboardConfig.GetConfig("Account", "username")[1]:
|
||||||
|
session['username'] = data['username']
|
||||||
|
resp = jsonify(ResponseObject(True))
|
||||||
|
resp.set_cookie("authToken",
|
||||||
|
hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest())
|
||||||
|
session.permanent = True
|
||||||
|
return resp
|
||||||
|
return jsonify(ResponseObject(False, "Username or password is incorrect."))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
engine = create_engine("sqlite:///" + os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'))
|
||||||
|
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
|
||||||
|
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
||||||
|
_getConfigurationList()
|
||||||
|
app.run(host=app_ip, debug=False, port=app_port)
|
@ -8,6 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app" class="vw-100 vh-100"></div>
|
<div id="app" class="vw-100 vh-100"></div>
|
||||||
<!-- <script type="module" src="./src/main.js"></script> -->
|
<script type="module" src="./src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
934
src/static/app/public/dashboard.css
Normal file
934
src/static/app/public/dashboard.css
Normal 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(2.1rem) translateX(1rem);
|
||||||
|
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%;
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav class="navbar bg-dark fixed-top" data-bs-theme="dark">
|
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<span class="navbar-brand mb-0 h1">WGDashboard</span>
|
<span class="navbar-brand mb-0 h1">WGDashboard</span>
|
||||||
</div>
|
</div>
|
||||||
|
43
src/static/app/src/components/navbar.vue
Normal file
43
src/static/app/src/components/navbar.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "navbar"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar border border-right p-0">
|
||||||
|
<div class="sidebar-sticky pt-3">
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item">
|
||||||
|
<RouterLink class="nav-link" to="/">Home</RouterLink></li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<RouterLink class="nav-link" to="/settings">Settings</RouterLink></li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||||
|
<span>Configurations</span>
|
||||||
|
</h6>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
|
||||||
|
</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>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item"><a href="https://github.com/donaldzou/WGDashboard/releases/tag/"><small class="nav-link text-muted"></small></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -2,6 +2,7 @@ import { createRouter, createWebHashHistory } from 'vue-router'
|
|||||||
import {cookie} from "../utilities/cookie.js";
|
import {cookie} from "../utilities/cookie.js";
|
||||||
import Index from "@/views/index.vue"
|
import Index from "@/views/index.vue"
|
||||||
import Signin from "@/views/signin.vue";
|
import Signin from "@/views/signin.vue";
|
||||||
|
import ConfigurationList from "@/views/configurationList.vue";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
@ -11,7 +12,13 @@ const router = createRouter({
|
|||||||
component: Index,
|
component: Index,
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true
|
requiresAuth: true
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: ConfigurationList
|
||||||
}
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/signin', component: Signin
|
path: '/signin', component: Signin
|
||||||
@ -21,7 +28,7 @@ const router = createRouter({
|
|||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
if (to.meta.requiresAuth){
|
if (to.meta.requiresAuth){
|
||||||
if (cookie.getCookie("auth")){
|
if (cookie.getCookie("authToken")){
|
||||||
next()
|
next()
|
||||||
}else{
|
}else{
|
||||||
next("/signin")
|
next("/signin")
|
||||||
@ -30,5 +37,4 @@ router.beforeEach((to, from, next) => {
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
13
src/static/app/src/views/configurationList.vue
Normal file
13
src/static/app/src/views/configurationList.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "configurationList"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -1,11 +1,21 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import Navbar from "@/components/navbar.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "index"
|
name: "index",
|
||||||
|
components: {Navbar}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="container-fluid flex-grow-1">
|
||||||
|
<div class="row h-100">
|
||||||
|
<Navbar></Navbar>
|
||||||
|
<main class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mb-4">
|
||||||
|
<RouterView></RouterView>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -6,17 +6,29 @@ export default {
|
|||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
username: "",
|
username: "",
|
||||||
password: ""
|
password: "",
|
||||||
|
loginError: false,
|
||||||
|
loginErrorMessage: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async auth(){
|
async auth(){
|
||||||
if (this.username && this.password){
|
if (this.username && this.password){
|
||||||
await fetchPost("/auth", {
|
await fetchPost("/api/authenticate", {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
password: this.password
|
password: this.password
|
||||||
}, (response) => {
|
}, (response) => {
|
||||||
console.log(response)
|
if (response.status){
|
||||||
|
this.loginError = false;
|
||||||
|
this.$router.push('/')
|
||||||
|
}else{
|
||||||
|
this.loginError = true;
|
||||||
|
this.loginErrorMessage = response.message;
|
||||||
|
document.querySelectorAll("input[required]").forEach(x => {
|
||||||
|
x.classList.remove("is-valid")
|
||||||
|
x.classList.add("is-invalid")
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
document.querySelectorAll("input[required]").forEach(x => {
|
document.querySelectorAll("input[required]").forEach(x => {
|
||||||
@ -27,7 +39,7 @@ export default {
|
|||||||
x.classList.remove("is-invalid")
|
x.classList.remove("is-invalid")
|
||||||
x.classList.add("is-valid")
|
x.classList.add("is-valid")
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,10 +49,13 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-fluid login-container-fluid h-100 d-flex">
|
<div class="container-fluid login-container-fluid h-100 d-flex">
|
||||||
<div class="login-box m-auto" style="width: 500px;">
|
<div class="login-box m-auto" style="width: 500px;">
|
||||||
<h1 class="text-center">Sign in</h1>
|
<h5 class="text-center">Welcome to</h5>
|
||||||
<h5 class="text-center">to WGDashboard</h5>
|
<h1 class="text-center">WGDashboard</h1>
|
||||||
<div class="m-auto">
|
<div class="m-auto">
|
||||||
<div class="alert alert-danger d-none" role="alert" style="margin-top: 1rem; margin-bottom: 0rem;"></div>
|
<div class="alert alert-danger mt-2 mb-0" role="alert" v-if="loginError">
|
||||||
|
{{this.loginErrorMessage}}
|
||||||
|
</div>
|
||||||
|
<form @submit="(e) => {e.preventDefault(); this.auth();}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username" class="text-left" style="font-size: 1rem"><i class="bi bi-person-circle"></i></label>
|
<label for="username" class="text-left" style="font-size: 1rem"><i class="bi bi-person-circle"></i></label>
|
||||||
<input type="text" v-model="username" class="form-control" id="username" name="username" placeholder="Username" required>
|
<input type="text" v-model="username" class="form-control" id="username" name="username" placeholder="Username" required>
|
||||||
@ -49,7 +64,9 @@ export default {
|
|||||||
<label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label>
|
<label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label>
|
||||||
<input type="password" v-model="password" class="form-control" id="password" name="password" placeholder="Password" required>
|
<input type="password" v-model="password" class="form-control" id="password" name="password" placeholder="Password" required>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-dark w-100 mt-4" @click="this.auth()">Sign In</button>
|
<button class="btn btn-dark ms-auto mt-4 w-100 d-flex">
|
||||||
|
Sign In<i class="ms-auto bi bi-chevron-right"></i></button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,7 +15,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server:{
|
server:{
|
||||||
proxy: {
|
proxy: {
|
||||||
'/': 'http://178.128.231.4:10086/'
|
'/api': 'http://178.128.231.4:10086/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
body {
|
#app{
|
||||||
font-size: .875rem;
|
display: flex;
|
||||||
/*font-family: 'Poppins', sans-serif;*/
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.codeFont{
|
.codeFont{
|
||||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
}
|
}
|
||||||
@ -21,34 +23,34 @@ body {
|
|||||||
* Sidebar
|
* Sidebar
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.sidebar {
|
/*.sidebar {*/
|
||||||
position: fixed;
|
/* position: fixed;*/
|
||||||
top: 0;
|
/* top: 0;*/
|
||||||
bottom: 0;
|
/* bottom: 0;*/
|
||||||
left: 0;
|
/* left: 0;*/
|
||||||
z-index: 100;
|
/* z-index: 100;*/
|
||||||
/* Behind the navbar */
|
/* !* Behind the navbar *!*/
|
||||||
padding: 48px 0 0;
|
/* padding: 48px 0 0;*/
|
||||||
/* Height of navbar */
|
/* !* Height of navbar *!*/
|
||||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
/* box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);*/
|
||||||
}
|
/*}*/
|
||||||
|
|
||||||
.sidebar-sticky {
|
/*.sidebar-sticky {*/
|
||||||
position: relative;
|
/* position: relative;*/
|
||||||
top: 0;
|
/* top: 0;*/
|
||||||
height: calc(100vh - 48px);
|
/* height: calc(100vh - 48px);*/
|
||||||
padding-top: .5rem;
|
/* padding-top: .5rem;*/
|
||||||
overflow-x: hidden;
|
/* overflow-x: hidden;*/
|
||||||
overflow-y: auto;
|
/* overflow-y: auto;*/
|
||||||
/* Scrollable contents if viewport is shorter than content. */
|
/* !* Scrollable contents if viewport is shorter than content. *!*/
|
||||||
}
|
/*}*/
|
||||||
|
|
||||||
@supports ((position: -webkit-sticky) or (position: sticky)) {
|
/*@supports ((position: -webkit-sticky) or (position: sticky)) {*/
|
||||||
.sidebar-sticky {
|
/* .sidebar-sticky {*/
|
||||||
position: -webkit-sticky;
|
/* position: -webkit-sticky;*/
|
||||||
position: sticky;
|
/* position: sticky;*/
|
||||||
}
|
/* }*/
|
||||||
}
|
/*}*/
|
||||||
|
|
||||||
.sidebar .nav-link, .bottomNavContainer .nav-link{
|
.sidebar .nav-link, .bottomNavContainer .nav-link{
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
Loading…
Reference in New Issue
Block a user