Rework project architecture with extensive bug fixes and security enhancements.

This commit is contained in:
2025-02-11 13:01:01 +01:00
parent b390b87fef
commit 6796dae280
25 changed files with 1584 additions and 2600 deletions

View File

@@ -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 &quot;-&quot; 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 &copy; <script>document.write(new Date().getFullYear())</script> <a href="https://github.com/swissmakers/wireguard-manager">WireGuard Manager</a>.</strong> All rights
reserved.
<strong>Copyright &copy; <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 -->

View File

@@ -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>