mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2026-03-21 16:06:18 +00:00
add dark mode support
This commit is contained in:
@@ -27,3 +27,99 @@ body.layout-boxed .wrapper {
|
||||
50% { opacity: 0.3; transform: scale(1.1); }
|
||||
100% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
|
||||
/* ── Peer list ── */
|
||||
.peer-extra-info {
|
||||
display: none;
|
||||
}
|
||||
.callout.position-relative {
|
||||
padding: 0 !important;
|
||||
}
|
||||
#inviteTextContainer {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
#inviteText {
|
||||
white-space: pre-line;
|
||||
text-align: left;
|
||||
}
|
||||
.div-peer-text-information {
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, white, transparent);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.peer-lock-icon {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 48px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
|
||||
/* ── Dark mode ── */
|
||||
body.dark-mode.layout-boxed {
|
||||
background: radial-gradient(circle at center, #1c2025 0%, #272c31 60%, #2d3238 100%);
|
||||
}
|
||||
body.dark-mode.layout-boxed .wrapper {
|
||||
background-color: #343a40;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
body.dark-mode pre {
|
||||
background-color: #2d3035;
|
||||
color: #dee2e6;
|
||||
border: 1px solid #4b545c;
|
||||
}
|
||||
|
||||
body.dark-mode .btn-primary {
|
||||
background-color: #3d8bcd;
|
||||
border-color: #3d8bcd;
|
||||
color: #fff;
|
||||
}
|
||||
body.dark-mode .btn-primary:hover,
|
||||
body.dark-mode .btn-primary:focus {
|
||||
background-color: #4d9eff;
|
||||
border-color: #4d9eff;
|
||||
color: #fff;
|
||||
}
|
||||
body.dark-mode .btn-outline-primary {
|
||||
color: #3d8bcd;
|
||||
border-color: #3d8bcd;
|
||||
}
|
||||
body.dark-mode .btn-outline-primary:hover,
|
||||
body.dark-mode .btn-outline-primary:focus {
|
||||
background-color: #3d8bcd;
|
||||
border-color: #3d8bcd;
|
||||
color: #fff;
|
||||
}
|
||||
body.dark-mode .text-primary {
|
||||
color: #3d8bcd !important;
|
||||
}
|
||||
body.dark-mode a {
|
||||
color: #3d8bcd;
|
||||
}
|
||||
body.dark-mode a:hover {
|
||||
color: #4d9eff;
|
||||
}
|
||||
body.dark-mode .card-primary.card-outline {
|
||||
border-top-color: #3d8bcd;
|
||||
}
|
||||
body.dark-mode .nav-pills .nav-link.active,
|
||||
body.dark-mode .nav-pills .show > .nav-link {
|
||||
background-color: #3d8bcd;
|
||||
}
|
||||
|
||||
body.dark-mode .div-peer-text-information {
|
||||
background: linear-gradient(to right, #3f474e, transparent);
|
||||
}
|
||||
body.dark-mode .div-peer-text-information h5 a {
|
||||
color: #dee2e6;
|
||||
}
|
||||
body.dark-mode .div-peer-text-information h5 a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -31,12 +31,16 @@
|
||||
<div class="col-md-6">
|
||||
<button type="submit" class="btn btn-primary btn-block">{% trans 'Login' %}</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<a class="btn btn-outline-primary btn-block" href="/change_language/">
|
||||
{% trans 'Language' %}
|
||||
<div class="col-md-3">
|
||||
<a class="btn btn-outline-primary btn-block" href="/change_language/" title="{% trans 'Change Language' %}">
|
||||
<i class="fas fa-language"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a class="btn btn-outline-primary btn-block" href="#" id="darkModeToggle" title="{% trans 'Toggle Dark Mode' %}">
|
||||
<i class="fas {% if request.COOKIES.darkMode == 'dark' %}fa-sun{% else %}fa-moon{% endif %}" id="darkModeIcon"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,15 @@
|
||||
{% block page_custom_head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body class="hold-transition sidebar-mini layout-boxed {% if request.COOKIES.sidebarState == 'collapsed' %}sidebar-collapse{% endif %}">
|
||||
<body class="hold-transition sidebar-mini layout-boxed {% if request.COOKIES.sidebarState == 'collapsed' %}sidebar-collapse{% endif %} {% if request.COOKIES.darkMode == 'dark' %}dark-mode{% endif %}">
|
||||
<script>
|
||||
(function() {
|
||||
var m = document.cookie.match(/(?:^|; )darkMode=([^;]*)/);
|
||||
if (!m && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<div class="wrapper">
|
||||
|
||||
<!-- Preloader -->
|
||||
|
||||
@@ -15,8 +15,66 @@
|
||||
<link rel="stylesheet" href="/static/AdminLTE-3.2.0/plugins/icheck-bootstrap/icheck-bootstrap.min.css">
|
||||
<!-- Theme style -->
|
||||
<link rel="stylesheet" href="/static/AdminLTE-3.2.0/dist/css/adminlte.min.css">
|
||||
|
||||
<style>
|
||||
body.dark-mode.register-page {
|
||||
background: #1c2025;
|
||||
}
|
||||
body.dark-mode .register-box .card {
|
||||
background-color: #343a40;
|
||||
color: #dee2e6;
|
||||
}
|
||||
body.dark-mode .register-box .card .form-control {
|
||||
background-color: #2d3238;
|
||||
border-color: #4b545c;
|
||||
color: #dee2e6;
|
||||
}
|
||||
body.dark-mode .register-box .card .form-control::placeholder {
|
||||
color: #8d9aaa;
|
||||
}
|
||||
body.dark-mode .register-box .card .input-group-text {
|
||||
background-color: #3f474e;
|
||||
border-color: #4b545c;
|
||||
color: #dee2e6;
|
||||
}
|
||||
body.dark-mode .register-box .register-logo a {
|
||||
color: #dee2e6;
|
||||
}
|
||||
body.dark-mode .btn-primary {
|
||||
background-color: #3d8bcd;
|
||||
border-color: #3d8bcd;
|
||||
color: #fff;
|
||||
}
|
||||
body.dark-mode .btn-primary:hover,
|
||||
body.dark-mode .btn-primary:focus {
|
||||
background-color: #4d9eff;
|
||||
border-color: #4d9eff;
|
||||
color: #fff;
|
||||
}
|
||||
body.dark-mode .btn-outline-primary {
|
||||
color: #3d8bcd;
|
||||
border-color: #3d8bcd;
|
||||
}
|
||||
body.dark-mode .btn-outline-primary:hover,
|
||||
body.dark-mode .btn-outline-primary:focus {
|
||||
background-color: #3d8bcd;
|
||||
border-color: #3d8bcd;
|
||||
color: #fff;
|
||||
}
|
||||
body.dark-mode .login-box-msg {
|
||||
color: #dee2e6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="hold-transition register-page">
|
||||
<body class="hold-transition register-page {% if request.COOKIES.darkMode == 'dark' %}dark-mode{% endif %}">
|
||||
<script>
|
||||
(function() {
|
||||
var m = document.cookie.match(/(?:^|; )darkMode=([^;]*)/);
|
||||
if (!m && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -26,6 +84,64 @@
|
||||
<script src="/static/AdminLTE-3.2.0/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- AdminLTE App -->
|
||||
<script src="/static/AdminLTE-3.2.0/dist/js/adminlte.min.js"></script>
|
||||
|
||||
<script>
|
||||
function setCookie(name, value, days) {
|
||||
var expires = "";
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days*24*60*60*1000));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function syncDarkModeUI() {
|
||||
var isDark = $('body').hasClass('dark-mode');
|
||||
$('#darkModeIcon').toggleClass('fa-sun', isDark).toggleClass('fa-moon', !isDark);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
syncDarkModeUI();
|
||||
|
||||
if (window.matchMedia) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
|
||||
if (!getCookie('darkMode')) {
|
||||
if (e.matches) {
|
||||
$('body').addClass('dark-mode');
|
||||
} else {
|
||||
$('body').removeClass('dark-mode');
|
||||
}
|
||||
syncDarkModeUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#darkModeToggle').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
if ($('body').hasClass('dark-mode')) {
|
||||
$('body').removeClass('dark-mode');
|
||||
setCookie('darkMode', 'light', 365);
|
||||
} else {
|
||||
$('body').addClass('dark-mode');
|
||||
setCookie('darkMode', 'dark', 365);
|
||||
}
|
||||
syncDarkModeUI();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% include 'template_parts/base_messages.html' %}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
<!-- Navbar -->
|
||||
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
|
||||
<nav class="main-header navbar navbar-expand {% if request.COOKIES.darkMode == 'dark' %}navbar-dark{% else %}navbar-white navbar-light{% endif %}">
|
||||
<!-- Left navbar links -->
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
@@ -10,6 +10,11 @@
|
||||
|
||||
<!-- Right navbar links -->
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link" id="darkModeToggle" title="{% trans 'Toggle Dark Mode' %}">
|
||||
<i class="fas {% if request.COOKIES.darkMode == 'dark' %}fa-sun{% else %}fa-moon{% endif %}" id="darkModeIcon"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/change_language/" class="nav-link" title="{% trans 'Change Language' %}">
|
||||
<i class="fas fa-language"></i>
|
||||
|
||||
@@ -104,6 +104,46 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function syncDarkModeUI() {
|
||||
var isDark = $('body').hasClass('dark-mode');
|
||||
if (isDark) {
|
||||
$('.main-header').removeClass('navbar-white navbar-light').addClass('navbar-dark');
|
||||
$('#darkModeIcon').removeClass('fa-moon').addClass('fa-sun');
|
||||
} else {
|
||||
$('.main-header').removeClass('navbar-dark').addClass('navbar-white navbar-light');
|
||||
$('#darkModeIcon').removeClass('fa-sun').addClass('fa-moon');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
syncDarkModeUI();
|
||||
|
||||
if (window.matchMedia) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
|
||||
if (!getCookie('darkMode')) {
|
||||
if (e.matches) {
|
||||
$('body').addClass('dark-mode');
|
||||
} else {
|
||||
$('body').removeClass('dark-mode');
|
||||
}
|
||||
syncDarkModeUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#darkModeToggle').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
if ($('body').hasClass('dark-mode')) {
|
||||
$('body').removeClass('dark-mode');
|
||||
setCookie('darkMode', 'light', 365);
|
||||
} else {
|
||||
$('body').addClass('dark-mode');
|
||||
setCookie('darkMode', 'dark', 365);
|
||||
}
|
||||
syncDarkModeUI();
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
var sidebarState = getCookie('sidebarState');
|
||||
if (sidebarState === 'collapsed') {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<style>
|
||||
.peer-extra-info {
|
||||
display: none;
|
||||
}
|
||||
.callout.position-relative {
|
||||
padding: 0 !important;
|
||||
}
|
||||
#inviteTextContainer {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
#inviteText {
|
||||
white-space: pre-line;
|
||||
text-align: left;
|
||||
}
|
||||
.div-peer-text-information {
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, white, transparent);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.peer-lock-icon {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 48px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
</style>
|
||||
@@ -95,7 +95,8 @@
|
||||
|
||||
$('#downloadConfigButton').attr('href', downloadUrl);
|
||||
$('#qrcodeButton').attr('href', qrUrl);
|
||||
$('#graphImg').attr('src', '/rrd/graph/?peer=' + uuid).show();
|
||||
var darkParam = $('body').hasClass('dark-mode') ? '&dark=1' : '';
|
||||
$('#graphImg').attr('src', '/rrd/graph/?peer=' + uuid + darkParam).show();
|
||||
$('#peerPreviewModal').data('peer-uuid', uuid);
|
||||
|
||||
$.ajax({
|
||||
@@ -130,7 +131,8 @@
|
||||
e.preventDefault();
|
||||
var period = $(this).data('period');
|
||||
var uuid = $('#peerPreviewModal').data('peer-uuid');
|
||||
var newSrc = '/rrd/graph/?peer=' + uuid + '&period=' + period;
|
||||
var darkParam = $('body').hasClass('dark-mode') ? '&dark=1' : '';
|
||||
var newSrc = '/rrd/graph/?peer=' + uuid + '&period=' + period + darkParam;
|
||||
$('#graphImg').attr('src', newSrc);
|
||||
});
|
||||
</script>
|
||||
@@ -74,7 +74,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<img src="/rrd/graph/?instance={{ instance.uuid }}&period={{ request.GET.period|default:'6h' }}"
|
||||
<img src="/rrd/graph/?instance={{ instance.uuid }}&period={{ request.GET.period|default:'6h' }}{% if request.COOKIES.darkMode == 'dark' %}&dark=1{% endif %}"
|
||||
class="img-fluid" alt="RRD Graph">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
</div>
|
||||
|
||||
<center>
|
||||
<img id="graphImg" src="/rrd/graph/?peer={{ current_peer.uuid }}{% if request.GET.period %}&period={{ request.GET.period }}{% endif %}"
|
||||
<img id="graphImg" src="/rrd/graph/?peer={{ current_peer.uuid }}{% if request.GET.period %}&period={{ request.GET.period }}{% endif %}{% if request.COOKIES.darkMode == 'dark' %}&dark=1{% endif %}"
|
||||
class="img-fluid" alt="{% trans 'No traffic history, please wait a few minutes' %}"
|
||||
onerror="this.onerror=null; this.style.display='none'; this.insertAdjacentHTML('afterend', this.alt);">
|
||||
</center>
|
||||
@@ -324,7 +324,8 @@
|
||||
button.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
var period = this.getAttribute('data-period');
|
||||
var newSrc = '/rrd/graph/?peer={{ current_peer.uuid }}&period=' + period;
|
||||
var darkParam = $('body').hasClass('dark-mode') ? '&dark=1' : '';
|
||||
var newSrc = '/rrd/graph/?peer={{ current_peer.uuid }}&period=' + period + darkParam;
|
||||
var imgElement = document.getElementById('graphImg');
|
||||
if (imgElement) {
|
||||
imgElement.setAttribute('src', newSrc);
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<div class="col-md-6">
|
||||
<h3>wg{{ wireguard_instance.instance_id }} {% if wireguard_instance.name %}({{ wireguard_instance.name }}){% endif %}</h3>
|
||||
<p><b><i class="fas fa-chart-area nav-icon"></i> {% trans 'Instance Traffic' %}</b></p>
|
||||
<img id="graphImg" src="/rrd/graph/?instance={{ wireguard_instance.uuid }}{% if request.GET.period %}&period={{ request.GET.period }}{% endif %}" class="img-fluid" alt="No traffic history, please wait a few minutes" onerror="this.onerror=null; this.style.display='none'; this.insertAdjacentHTML('afterend', this.alt);">
|
||||
<img id="graphImg" src="/rrd/graph/?instance={{ wireguard_instance.uuid }}{% if request.GET.period %}&period={{ request.GET.period }}{% endif %}{% if request.COOKIES.darkMode == 'dark' %}&dark=1{% endif %}" class="img-fluid" alt="No traffic history, please wait a few minutes" onerror="this.onerror=null; this.style.display='none'; this.insertAdjacentHTML('afterend', this.alt);">
|
||||
<p>
|
||||
<b><i class="fas fa-network-wired nav-icon"></i> {% trans 'IP Address' %}: </b>{{ wireguard_instance.address }}/{{ wireguard_instance.netmask }}<br>
|
||||
<b><i class="fas fa-link nav-icon"></i> {% trans 'Public Address' %}: </b>{{ wireguard_instance.hostname }}<br>
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from user_manager.models import UserAcl
|
||||
from wgwadmlibrary.tools import user_has_access_to_peer
|
||||
from wireguard.models import Peer, WireGuardInstance
|
||||
|
||||
import base64
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
|
||||
@login_required
|
||||
def view_rrd_graph(request):
|
||||
@@ -45,21 +44,44 @@ def view_rrd_graph(request):
|
||||
if not (period[:-1].isdigit() and period[-1] in ['h', 'd']):
|
||||
period = '6h'
|
||||
|
||||
dark_mode = request.GET.get('dark') == '1'
|
||||
|
||||
command = [
|
||||
"rrdtool", "graph", graph_file,
|
||||
"--start", f"-{period}",
|
||||
"--title", f"{graph_title}",
|
||||
"--vertical-label", "Value",
|
||||
]
|
||||
|
||||
if dark_mode:
|
||||
command += [
|
||||
"--color", "BACK#2d3035",
|
||||
"--color", "CANVAS#353a40",
|
||||
"--color", "SHADEA#2d3035",
|
||||
"--color", "SHADEB#2d3035",
|
||||
"--color", "GRID#555c63",
|
||||
"--color", "MGRID#6c7580",
|
||||
"--color", "FONT#c8c8c8",
|
||||
"--color", "FRAME#2d3035",
|
||||
"--color", "ARROW#c8c8c8",
|
||||
]
|
||||
tx_color = "4a9eff"
|
||||
rx_color = "ff6b6b"
|
||||
else:
|
||||
tx_color = "0000FF"
|
||||
rx_color = "FF0000"
|
||||
|
||||
command += [
|
||||
f"DEF:txdata={rrd_file_path}:tx:AVERAGE",
|
||||
f"DEF:rxdata={rrd_file_path}:rx:AVERAGE",
|
||||
"CDEF:tx_mb=txdata,1048576,/",
|
||||
"CDEF:rx_mb=rxdata,1048576,/",
|
||||
"VDEF:tx_total=tx_mb,TOTAL",
|
||||
"VDEF:rx_total=rx_mb,TOTAL",
|
||||
"LINE1:txdata#0000FF:Transmitted ",
|
||||
f"LINE1:txdata#{tx_color}:Transmitted ",
|
||||
"GPRINT:tx_total:%6.2lf MB",
|
||||
"COMMENT:\\n",
|
||||
"LINE1:rxdata#FF0000:Received ",
|
||||
f"LINE1:rxdata#{rx_color}:Received ",
|
||||
"GPRINT:rx_total:%6.2lf MB",
|
||||
"COMMENT:\\n"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user