mirror of
https://github.com/swissmakers/wireguard-manager.git
synced 2026-03-26 13:03:28 +01:00
Rework project architecture with extensive bug fixes and security enhancements.
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
<!-- START: On page css -->
|
||||
{{template "top_css" .}}
|
||||
<!-- END: On page css -->
|
||||
<style>
|
||||
<style>
|
||||
/* Base Dark Mode Styles */
|
||||
body, .content-wrapper {
|
||||
background-color: #121212;
|
||||
@@ -78,9 +78,9 @@
|
||||
}
|
||||
/* Modify inputs and form elements */
|
||||
input, select, textarea, .form-control, .form-control:disabled, div.tagsinput {
|
||||
background-color: #333333;
|
||||
background-color: #333333 !important;
|
||||
color: #e0e0e0 !important;
|
||||
border: 1px solid #555;
|
||||
border: 1px solid #555 !important;
|
||||
}
|
||||
input::placeholder, select::placeholder, textarea::placeholder {
|
||||
color: #b0b0b0;
|
||||
@@ -123,7 +123,7 @@
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="hold-transition sidebar-mini">
|
||||
@@ -165,11 +165,9 @@
|
||||
<!-- Right navbar links -->
|
||||
<div class="navbar-nav ml-auto">
|
||||
<button style="margin-left: 0.5em;" type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal"
|
||||
data-target="#modal_new_client"><i class="nav-icon fas fa-plus"></i> New
|
||||
Client</button>
|
||||
data-target="#modal_new_client"><i class="nav-icon fas fa-plus"></i> New Client</button>
|
||||
<button id="apply-config-button" style="margin-left: 0.5em; display: none;" type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal"
|
||||
data-target="#modal_apply_config"><i class="nav-icon fas fa-check"></i> Apply
|
||||
Config</button>
|
||||
data-target="#modal_apply_config"><i class="nav-icon fas fa-check"></i> Apply Config</button>
|
||||
{{if .baseData.CurrentUser}}
|
||||
<button onclick="location.href='{{.basePath}}/logout';" style="margin-left: 0.5em;" type="button"
|
||||
class="btn btn-outline-danger btn-sm"><i class="nav-icon fas fa-sign-out-alt"></i> Logout</button>
|
||||
@@ -194,13 +192,7 @@
|
||||
</div>
|
||||
<div class="info">
|
||||
{{if .baseData.CurrentUser}}
|
||||
|
||||
{{if .baseData.Admin}}
|
||||
<a href="{{.basePath}}/profile" class="d-block">My Account: {{.baseData.CurrentUser}}</a>
|
||||
{{else}}
|
||||
<a href="{{.basePath}}/profile" class="d-block">My Account: {{.baseData.CurrentUser}}</a>
|
||||
{{end}}
|
||||
|
||||
{{else}}
|
||||
<a href="#" class="d-block">My Account</a>
|
||||
{{end}}
|
||||
@@ -214,17 +206,13 @@
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/" class="nav-link {{if eq .baseData.Active ""}}active{{end}}">
|
||||
<i class="nav-icon fas fa-user-secret"></i>
|
||||
<p>
|
||||
VPN Clients
|
||||
</p>
|
||||
<p>VPN Clients</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/status" class="nav-link {{if eq .baseData.Active "status" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-signal"></i>
|
||||
<p>
|
||||
VPN Connected
|
||||
</p>
|
||||
<p>VPN Connected</p>
|
||||
</a>
|
||||
</li>
|
||||
{{if .baseData.Admin}}
|
||||
@@ -232,26 +220,20 @@
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-server"></i>
|
||||
<p>
|
||||
WireGuard Server
|
||||
</p>
|
||||
<p>WireGuard Server</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/global-settings" class="nav-link {{if eq .baseData.Active "global-settings" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-cog"></i>
|
||||
<p>
|
||||
Client Config Settings
|
||||
</p>
|
||||
<p>Client Config Settings</p>
|
||||
</a>
|
||||
</li>
|
||||
{{if not .loginDisabled}}
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/users-settings" class="nav-link {{if eq .baseData.Active "users-settings" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-cog"></i>
|
||||
<p>
|
||||
WGM User Accounts
|
||||
</p>
|
||||
<i class="nav-icon fas fa-cog"></i>
|
||||
<p>WGM User Accounts</p>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
@@ -274,6 +256,7 @@
|
||||
</div>
|
||||
<form name="frm_new_client" id="frm_new_client">
|
||||
<div class="modal-body">
|
||||
<!-- Form fields for new client go here -->
|
||||
<div class="form-group">
|
||||
<label for="client_name" class="control-label">Name</label>
|
||||
<input type="text" class="form-control" id="client_name" name="client_name">
|
||||
@@ -284,8 +267,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="subnet_ranges" class="control-label">Subnet range</label>
|
||||
<select id="subnet_ranges" class="select2"
|
||||
data-placeholder="Select a subnet range" style="width: 100%;">
|
||||
<select id="subnet_ranges" class="select2" data-placeholder="Select a subnet range" style="width: 100%;">
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -294,22 +276,17 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="client_allowed_ips" class="control-label">Allowed IPs
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip"
|
||||
data-original-title="Specify a list of addresses that will get routed to the
|
||||
server. These addresses will be included in 'AllowedIPs' of client config">
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip" data-original-title="Specify a list of addresses that will get routed to the server. These addresses will be included in 'AllowedIPs' of client config">
|
||||
</i>
|
||||
</label>
|
||||
<input type="text" data-role="tagsinput" class="form-control" id="client_allowed_ips"
|
||||
value="{{ StringsJoin .client_defaults.AllowedIps "," }}">
|
||||
<input type="text" data-role="tagsinput" class="form-control" id="client_allowed_ips" value="{{ StringsJoin .client_defaults.AllowedIPs "," }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="client_extra_allowed_ips" class="control-label">Extra Allowed IPs
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip"
|
||||
data-original-title="Specify a list of addresses that will get routed to the
|
||||
client. These addresses will be included in 'AllowedIPs' of WG server config">
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip" data-original-title="Specify a list of addresses that will get routed to the client. These addresses will be included in 'AllowedIPs' of WG server config">
|
||||
</i>
|
||||
</label>
|
||||
<input type="text" data-role="tagsinput" class="form-control" id="client_extra_allowed_ips" value="{{ StringsJoin .client_defaults.ExtraAllowedIps "," }}">
|
||||
<input type="text" data-role="tagsinput" class="form-control" id="client_extra_allowed_ips" value="{{ StringsJoin .client_defaults.ExtraAllowedIPs "," }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="client_endpoint" class="control-label">Endpoint</label>
|
||||
@@ -318,43 +295,32 @@
|
||||
<div class="form-group">
|
||||
<div class="icheck-primary d-inline">
|
||||
<input type="checkbox" id="use_server_dns" {{ if .client_defaults.UseServerDNS }}checked{{ end }}>
|
||||
<label for="use_server_dns">
|
||||
Use server DNS
|
||||
</label>
|
||||
<label for="use_server_dns">Use server DNS</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="icheck-primary d-inline">
|
||||
<input type="checkbox" id="enabled" {{ if .client_defaults.EnableAfterCreation }}checked{{ end }}>
|
||||
<label for="enabled">
|
||||
Enable after creation
|
||||
</label>
|
||||
<label for="enabled">Enable after creation</label>
|
||||
</div>
|
||||
</div>
|
||||
<details>
|
||||
<summary><strong>Public and Preshared Keys</strong>
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip"
|
||||
data-original-title="If you don't want to let the server generate and store the
|
||||
client's private key, you can manually specify its public and preshared key here
|
||||
. Note: QR code will not be generated">
|
||||
<summary>
|
||||
<strong>Public and Preshared Keys</strong>
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip" data-original-title="If you don't want the server to generate and store the client's private key, you can manually specify its public and preshared key here. Note: QR code will not be generated">
|
||||
</i>
|
||||
</summary>
|
||||
<div class="form-group" style="margin-top: 1rem">
|
||||
<label for="client_public_key" class="control-label">
|
||||
Public Key
|
||||
</label>
|
||||
<label for="client_public_key" class="control-label">Public Key</label>
|
||||
<input type="text" class="form-control" id="client_public_key" name="client_public_key" placeholder="Autogenerated" aria-invalid="false">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="client_preshared_key" class="control-label">
|
||||
Preshared Key
|
||||
</label>
|
||||
<label for="client_preshared_key" class="control-label">Preshared Key</label>
|
||||
<input type="text" class="form-control" id="client_preshared_key" name="client_preshared_key" placeholder="Autogenerated - enter "-" to skip generation">
|
||||
</div>
|
||||
</details>
|
||||
<details style="margin-top: 0.5rem;">
|
||||
<summary><strong>Additional configuration</strong>
|
||||
</summary>
|
||||
<summary><strong>Additional configuration</strong></summary>
|
||||
<div class="form-group">
|
||||
<label for="additional_notes" class="control-label">Notes</label>
|
||||
<textarea class="form-control" style="min-height: 6rem;" id="additional_notes" name="additional_notes" placeholder="Additional notes about this client"></textarea>
|
||||
@@ -419,8 +385,7 @@
|
||||
<div class="float-right d-none d-sm-block">
|
||||
<b>Version</b> {{ .appVersion }}
|
||||
</div>
|
||||
<strong>Copyright © <script>document.write(new Date().getFullYear())</script> <a href="https://github.com/swissmakers/wireguard-manager">WireGuard Manager</a>.</strong> All rights
|
||||
reserved.
|
||||
<strong>Copyright © <script>document.write(new Date().getFullYear())</script> <a href="https://github.com/swissmakers/wireguard-manager">WireGuard Manager</a>.</strong> All rights reserved.
|
||||
</footer>
|
||||
|
||||
<!-- Control Sidebar -->
|
||||
@@ -463,50 +428,54 @@
|
||||
`, 'toastrToastStyleFix')
|
||||
|
||||
toastr.options.closeDuration = 100;
|
||||
// toastr.options.timeOut = 10000;
|
||||
toastr.options.positionClass = 'toast-top-right-fix';
|
||||
|
||||
updateApplyConfigVisibility()
|
||||
|
||||
// Initial call, and then poll every 5 seconds for config changes
|
||||
updateApplyConfigVisibility();
|
||||
setInterval(updateApplyConfigVisibility, 5000);
|
||||
});
|
||||
|
||||
function addGlobalStyle(css, id) {
|
||||
if (!document.querySelector('#' + id)) {
|
||||
let head = document.head
|
||||
if (!head) { return }
|
||||
let style = document.createElement('style')
|
||||
style.type = 'text/css'
|
||||
style.id = id
|
||||
style.innerHTML = css
|
||||
head.appendChild(style)
|
||||
let head = document.head;
|
||||
if (!head) { return; }
|
||||
let style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.id = id;
|
||||
style.innerHTML = css;
|
||||
head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
function updateApplyConfigVisibility() {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/test-hash',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function(data) {
|
||||
if (data.status) {
|
||||
$("#apply-config-button").show()
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#apply-config-button").hide()
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/test-hash',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function(data) {
|
||||
console.log("Config check response:", data);
|
||||
// Check the 'success' property returned by the endpoint.
|
||||
if (data.success) {
|
||||
$("#apply-config-button").show();
|
||||
} else {
|
||||
$("#apply-config-button").hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
try {
|
||||
const responseJson = JSON.parse(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
} catch (e) {
|
||||
toastr.error("Error checking config changes.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// populateClient function for render new client info
|
||||
// on the client page.
|
||||
// populateClient function for render new client info on the client page.
|
||||
function populateClient(client_id) {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
@@ -518,44 +487,42 @@
|
||||
renderClientList([resp]);
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
try {
|
||||
const responseJson = JSON.parse(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
} catch (e) {
|
||||
toastr.error("Error loading client data.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// submitNewClient function for new client form submission
|
||||
// submitNewClient function for new client form submission.
|
||||
function submitNewClient() {
|
||||
const name = $("#client_name").val();
|
||||
const email = $("#client_email").val();
|
||||
const allocated_ips = $("#client_allocated_ips").val().split(",");
|
||||
const allowed_ips = $("#client_allowed_ips").val().split(",");
|
||||
const endpoint = $("#client_endpoint").val();
|
||||
let use_server_dns = false;
|
||||
let extra_allowed_ips = [];
|
||||
|
||||
if ($("#client_extra_allowed_ips").val() !== "") {
|
||||
extra_allowed_ips = $("#client_extra_allowed_ips").val().split(",");
|
||||
}
|
||||
|
||||
|
||||
if ($("#use_server_dns").is(':checked')){
|
||||
use_server_dns = true;
|
||||
}
|
||||
|
||||
let enabled = false;
|
||||
|
||||
if ($("#enabled").is(':checked')){
|
||||
enabled = true;
|
||||
}
|
||||
const use_server_dns = $("#use_server_dns").is(':checked');
|
||||
const enabled = $("#enabled").is(':checked');
|
||||
const public_key = $("#client_public_key").val();
|
||||
const preshared_key = $("#client_preshared_key").val();
|
||||
|
||||
const additional_notes = $("#additional_notes").val();
|
||||
|
||||
const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips,
|
||||
"extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, "use_server_dns": use_server_dns, "enabled": enabled,
|
||||
"public_key": public_key, "preshared_key": preshared_key, "additional_notes": additional_notes};
|
||||
const data = {
|
||||
"name": name,
|
||||
"email": email,
|
||||
"allocated_ips": allocated_ips,
|
||||
"allowed_ips": allowed_ips,
|
||||
"extra_allowed_ips": $("#client_extra_allowed_ips").val().split(","),
|
||||
"endpoint": endpoint,
|
||||
"use_server_dns": use_server_dns,
|
||||
"enabled": enabled,
|
||||
"public_key": public_key,
|
||||
"preshared_key": preshared_key,
|
||||
"additional_notes": additional_notes
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
@@ -567,26 +534,30 @@
|
||||
success: function(resp) {
|
||||
$("#modal_new_client").modal('hide');
|
||||
toastr.success('Created new client successfully');
|
||||
// Update the home page (clients page) after adding successfully
|
||||
// Update the home page (clients page) after adding successfully.
|
||||
if (window.location.pathname === "{{.basePath}}/") {
|
||||
populateClient(resp.id);
|
||||
}
|
||||
updateApplyConfigVisibility()
|
||||
updateApplyConfigVisibility();
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
try {
|
||||
const responseJson = JSON.parse(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
} catch (e) {
|
||||
toastr.error("Error creating client.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// updateIPAllocationSuggestion function for automatically fill
|
||||
// the IP Allocation input with suggested ip addresses
|
||||
// updateIPAllocationSuggestion function for automatically filling
|
||||
// the IP Allocation input with suggested IP addresses.
|
||||
function updateIPAllocationSuggestion(forceDefault = false) {
|
||||
let subnetRange = $("#subnet_ranges").select2('val');
|
||||
|
||||
if (forceDefault || !subnetRange || subnetRange.length === 0) {
|
||||
subnetRange = '__default_any__'
|
||||
subnetRange = '__default_any__';
|
||||
}
|
||||
$.ajax({
|
||||
cache: false,
|
||||
@@ -596,29 +567,33 @@
|
||||
contentType: "application/json",
|
||||
success: function(data) {
|
||||
const allocated_ips = $("#client_allocated_ips").val().split(",");
|
||||
allocated_ips.forEach(function (item, index) {
|
||||
$('#client_allocated_ips').removeTag(escape(item));
|
||||
})
|
||||
data.forEach(function (item, index) {
|
||||
allocated_ips.forEach(function (item) {
|
||||
$('#client_allocated_ips').removeTag(item);
|
||||
});
|
||||
data.forEach(function (item) {
|
||||
$('#client_allocated_ips').addTag(item);
|
||||
})
|
||||
});
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const allocated_ips = $("#client_allocated_ips").val().split(",");
|
||||
allocated_ips.forEach(function (item, index) {
|
||||
$('#client_allocated_ips').removeTag(escape(item));
|
||||
})
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
allocated_ips.forEach(function (item) {
|
||||
$('#client_allocated_ips').removeTag(item);
|
||||
});
|
||||
try {
|
||||
const responseJson = JSON.parse(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
} catch (e) {
|
||||
toastr.error("Error suggesting IP allocation.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
//Initialize Select2 Elements
|
||||
$(".select2").select2()
|
||||
// Initialize Select2 Elements.
|
||||
$(".select2").select2();
|
||||
|
||||
// IP Allocation tag input
|
||||
// IP Allocation tag input.
|
||||
$("#client_allocated_ips").tagsInput({
|
||||
'width': '100%',
|
||||
'height': '75%',
|
||||
@@ -630,7 +605,7 @@
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
// AllowedIPs tag input
|
||||
// AllowedIPs tag input.
|
||||
$("#client_allowed_ips").tagsInput({
|
||||
'width': '100%',
|
||||
'height': '75%',
|
||||
@@ -653,7 +628,7 @@
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
// New client form validation
|
||||
// New client form validation.
|
||||
$(document).ready(function () {
|
||||
$.validator.setDefaults({
|
||||
submitHandler: function () {
|
||||
@@ -676,18 +651,18 @@
|
||||
error.addClass('invalid-feedback');
|
||||
element.closest('.form-group').append(error);
|
||||
},
|
||||
highlight: function (element, errorClass, validClass) {
|
||||
highlight: function (element) {
|
||||
$(element).addClass('is-invalid');
|
||||
},
|
||||
unhighlight: function (element, errorClass, validClass) {
|
||||
unhighlight: function (element) {
|
||||
$(element).removeClass('is-invalid');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// New Client modal event
|
||||
// New Client modal event.
|
||||
$(document).ready(function () {
|
||||
$("#modal_new_client").on('shown.bs.modal', function (e) {
|
||||
$("#modal_new_client").on('shown.bs.modal', function () {
|
||||
$("#client_name").val("");
|
||||
$("#client_email").val("");
|
||||
$("#client_public_key").val("");
|
||||
@@ -701,13 +676,12 @@
|
||||
});
|
||||
});
|
||||
|
||||
// handle subnet range select
|
||||
$('#subnet_ranges').on('select2:select', function (e) {
|
||||
// console.log('Selected Option: ', $("#subnet_ranges").select2('val'));
|
||||
// Handle subnet range select.
|
||||
$('#subnet_ranges').on('select2:select', function () {
|
||||
updateIPAllocationSuggestion();
|
||||
});
|
||||
|
||||
// apply_config_confirm button event
|
||||
// apply_config_confirm button event.
|
||||
$(document).ready(function () {
|
||||
$("#apply_config_confirm").click(function () {
|
||||
$.ajax({
|
||||
@@ -717,19 +691,22 @@
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function(data) {
|
||||
updateApplyConfigVisibility()
|
||||
updateApplyConfigVisibility();
|
||||
$("#modal_apply_config").modal('hide');
|
||||
toastr.success('Applied config successfully');
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
error: function(jqXHR) {
|
||||
try {
|
||||
const responseJson = JSON.parse(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
} catch (e) {
|
||||
toastr.error("Error applying config.");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- START: On page script -->
|
||||
{{template "bottom_js" .}}
|
||||
<!-- END: On page script -->
|
||||
|
||||
@@ -1,189 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Login</title>
|
||||
<!-- Tell the browser to be responsive to screen width -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="{{.basePath}}/favicon">
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/fontawesome-free/css/all.min.css">
|
||||
<!-- icheck bootstrap -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/icheck-bootstrap/icheck-bootstrap.min.css">
|
||||
<!-- Theme style -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/dist/css/adminlte.min.css">
|
||||
<style>
|
||||
/* Base Dark Mode Styles */
|
||||
body, .content-wrapper, .login-page {
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.main-footer {
|
||||
background-color: #1c1c1c;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.card {
|
||||
background-color: #2a2a2a;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
/* Dark mode for buttons */
|
||||
.btn-outline-primary {
|
||||
border-color: #4e73df;
|
||||
color: #4e73df;
|
||||
}
|
||||
.btn-outline-primary:hover {
|
||||
background-color: #4e73df;
|
||||
color: #ffffff;
|
||||
}
|
||||
.btn-outline-danger {
|
||||
border-color: #e74a3b;
|
||||
color: #e74a3b;
|
||||
}
|
||||
.btn-outline-danger:hover {
|
||||
background-color: #e74a3b;
|
||||
color: #ffffff;
|
||||
}
|
||||
/* Modify inputs and form elements */
|
||||
input, select, textarea, .form-control, .form-control:disabled, div.tagsinput {
|
||||
background-color: #333333;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
input::placeholder, select::placeholder, textarea::placeholder {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
background-color: #444;
|
||||
}
|
||||
/* Modal dark mode */
|
||||
.modal-content {
|
||||
background-color: #2a2a2a;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.modal-header {
|
||||
border-bottom: 1px solid #555;
|
||||
}
|
||||
.modal-footer {
|
||||
border-top: 1px solid #555;
|
||||
}
|
||||
/* Dark mode for the sidebar active state */
|
||||
.nav-sidebar .nav-link.active {
|
||||
background-color: #444;
|
||||
}
|
||||
/* Table dark mode */
|
||||
table {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
table th, table td {
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="hold-transition login-page">
|
||||
<div class="login-box">
|
||||
<div class="card">
|
||||
<div class="card-body login-card-body">
|
||||
<p class="login-box-msg">Sign in to start your session</p>
|
||||
<form action="" method="post">
|
||||
<div class="input-group mb-3">
|
||||
<input id="username" type="text" class="form-control" placeholder="Username">
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-envelope"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<input id="password" type="password" class="form-control" placeholder="Password">
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="icheck-primary">
|
||||
<input type="checkbox" id="remember">
|
||||
<label for="remember">
|
||||
Remember Me
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col-4">
|
||||
<button id="btn_login" type="submit" class="btn btn-primary btn-block">Sign In</button>
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center mb-3">
|
||||
<p id="message"></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.login-card-body -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.login-box -->
|
||||
<!-- jQuery -->
|
||||
<script src="{{.basePath}}/static/plugins/jquery/jquery.min.js"></script>
|
||||
<!-- Bootstrap 4 -->
|
||||
<script src="{{.basePath}}/static/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- AdminLTE App -->
|
||||
<script src="{{.basePath}}/static/dist/js/adminlte.min.js"></script>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
function redirectNext() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const nextURL = urlParams.get('next');
|
||||
if (nextURL && /(?:^\/[a-zA-Z_])|(?:^\/$)/.test(nextURL.trim())) {
|
||||
window.location.href = nextURL;
|
||||
} else {
|
||||
window.location.href = '/{{.basePath}}';
|
||||
}
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Login</title>
|
||||
<!-- Responsive -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="{{.basePath}}/favicon">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/fontawesome-free/css/all.min.css">
|
||||
<!-- icheck bootstrap -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/icheck-bootstrap/icheck-bootstrap.min.css">
|
||||
<!-- Theme style -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/dist/css/adminlte.min.css">
|
||||
<style>
|
||||
/* Dark Mode Styles */
|
||||
.login-card-body, .register-card-body {
|
||||
background-color: #2b2b2b;
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
.login-card-body, .register-card-body {
|
||||
color: #cfcfcf;
|
||||
}
|
||||
body, .content-wrapper, .login-page {
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
/* Buttons */
|
||||
.btn-outline-primary {
|
||||
border-color: #4e73df;
|
||||
color: #4e73df;
|
||||
}
|
||||
.btn-outline-primary:hover {
|
||||
background-color: #4e73df;
|
||||
color: #ffffff;
|
||||
}
|
||||
/* Form elements */
|
||||
input, select, textarea, .form-control, .form-control:disabled, div.tagsinput {
|
||||
background-color: #333333 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
input::placeholder, select::placeholder, textarea::placeholder {
|
||||
color: #b0b0b0;
|
||||
}
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
background-color: #444;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="hold-transition login-page">
|
||||
<div class="login-box">
|
||||
<div class="card">
|
||||
<div class="card-body login-card-body">
|
||||
<p class="login-box-msg">Sign in to start your session</p>
|
||||
<form id="loginForm" method="post" novalidate>
|
||||
<div class="input-group mb-3">
|
||||
<input id="username" type="text" class="form-control" placeholder="Username" required>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-envelope"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<input id="password" type="password" class="form-control" placeholder="Password" required>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="icheck-primary">
|
||||
<input type="checkbox" id="remember">
|
||||
<label for="remember">Remember Me</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<button id="btn_login" type="submit" class="btn btn-primary btn-block">Sign In</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center mb-3">
|
||||
<p id="message"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- jQuery -->
|
||||
<script src="{{.basePath}}/static/plugins/jquery/jquery.min.js"></script>
|
||||
<!-- Bootstrap 4 -->
|
||||
<script src="{{.basePath}}/static/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- AdminLTE App -->
|
||||
<script src="{{.basePath}}/static/dist/js/adminlte.min.js"></script>
|
||||
<script>
|
||||
// Redirect based on 'next' URL parameter; default to basePath.
|
||||
function redirectNext() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const nextURL = urlParams.get('next');
|
||||
if (nextURL && nextURL.trim().startsWith("/")) {
|
||||
window.location.href = nextURL.trim();
|
||||
} else {
|
||||
window.location.href = '{{.basePath}}';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$("#btn_login").trigger('click');
|
||||
});
|
||||
|
||||
$("#btn_login").click(function () {
|
||||
const username = $("#username").val();
|
||||
const password = $("#password").val();
|
||||
let rememberMe = false;
|
||||
if ($("#remember").is(':checked')){
|
||||
rememberMe = true;
|
||||
// Override default form submission.
|
||||
$("#loginForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$("#btn_login").trigger('click');
|
||||
});
|
||||
$("#btn_login").click(function () {
|
||||
const username = $("#username").val().trim();
|
||||
const password = $("#password").val();
|
||||
const rememberMe = $("#remember").is(':checked');
|
||||
const data = { "username": username, "password": password, "rememberMe": rememberMe };
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/login',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function(response) {
|
||||
$("#message").html(`<p style="color:green">${response.message}</p>`);
|
||||
redirectNext();
|
||||
},
|
||||
error: function(jqXHR) {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(jqXHR.responseText);
|
||||
} catch (error) {
|
||||
response = { message: "An unexpected error occurred." };
|
||||
}
|
||||
const data = {"username": username, "password": password, "rememberMe": rememberMe}
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/login',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function(data) {
|
||||
document.getElementById("message").innerHTML = `<p style="color:green">${data['message']}</p>`;
|
||||
// redirect after logging in successfully
|
||||
redirectNext();
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
document.getElementById("message").innerHTML = `<p style="color:#ff0000">${responseJson['message']}</p>`;
|
||||
}
|
||||
});
|
||||
$("#message").html(`<p style="color:#ff0000">${response.message}</p>`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user