Merge branch 'master' into email-settings-UI

This commit is contained in:
Arminas 2023-03-15 22:58:07 +02:00 committed by GitHub
commit 571e34eeb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1186 additions and 177 deletions

View file

@ -30,6 +30,12 @@ About
<label for="version" class="control-label">Current version</label>
<input type="text" class="form-control" id="version" value="{{ .appVersion }}" readonly>
</div>
{{ if .gitCommit }}
<div class="form-group">
<label for="version" class="control-label">git commit hash</label>
<input type="text" class="form-control" id="version" value="{{ .gitCommit }}" readonly>
</div>
{{ end }}
<div class="form-group">
<label for="currentReleaseDate" class="control-label">Current version release date</label>
<input type="text" class="form-control" id="currentReleaseDate" readonly>

View file

@ -56,6 +56,13 @@
</button>
</div>
</div>
<select name="status-selector" id="status-selector" class="form-control selectpicker show-tick" style="margin-left: 10px">
<option value="All">All</option>
<option value="Enabled">Enabled</option>
<option value="Disabled">Disabled</option>
<option value="Connected">Connected</option>
<option value="Disconnected">Disconnected</option>
</select>
</form>
<!-- Right navbar links -->
@ -63,7 +70,7 @@
<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>
<button style="margin-left: 0.5em;" type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal"
<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>
{{if .baseData.CurrentUser}}
@ -90,7 +97,13 @@
</div>
<div class="info">
{{if .baseData.CurrentUser}}
<a href="{{.basePath}}/profile" class="d-block">{{.baseData.CurrentUser}}</a>
{{if .baseData.Admin}}
<a href="{{.basePath}}/profile" class="d-block">Administrator: {{.baseData.CurrentUser}}</a>
{{else}}
<a href="{{.basePath}}/profile" class="d-block">Manager: {{.baseData.CurrentUser}}</a>
{{end}}
{{else}}
<a href="#" class="d-block">Administrator</a>
{{end}}
@ -109,6 +122,8 @@
</p>
</a>
</li>
{{if .baseData.Admin}}
<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>
@ -117,6 +132,8 @@
</p>
</a>
</li>
<li class="nav-header">SETTINGS</li>
<li class="nav-item">
<a href="{{.basePath}}/global-settings" class="nav-link {{if eq .baseData.Active "global-settings" }}active{{end}}">
@ -134,6 +151,14 @@
</p>
</a>
</li>
<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>
Users Settings
</p>
</a>
</li>
{{end}}
<li class="nav-header">UTILITIES</li>
<li class="nav-item">
<a href="{{.basePath}}/status" class="nav-link {{if eq .baseData.Active "status" }}active{{end}}">
@ -347,6 +372,32 @@
$('[data-toggle="tooltip"]').tooltip()
})
$(document).ready(function () {
$.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']);
}
});
});
// populateClient function for render new client info
// on the client page.
function populateClient(client_id) {

View file

@ -70,17 +70,8 @@ Wireguard Clients
</div>
<div class="modal-body">
<input type="hidden" id="qr_client_id" name="qr_client_id">
<a href="" download="" id="qr_code_a">
<img id="qr_code" class="w-100" style="image-rendering: pixelated;" src="" alt="QR code" />
</a>
<div class="form-group">
<div class="icheck-primary d-inline">
<input type="checkbox" id="qr_include_fwmark" onchange="regenerateQRCode()">
<label for="qr_include_fwmark">
Include FwMark
</label>
</div>
</div>
<img id="qr_code" class="w-100" style="image-rendering: pixelated;" src="" alt="QR code" />
<!-- do not include FwMark in any client configs: it is INVALID. -->
</div>
</div>
<!-- /.modal-content -->
@ -263,6 +254,7 @@ Wireguard Clients
// hide all clients and display only the ones that meet the search criteria (name, email, IP)
$('#search-input').keyup(function () {
$("#status-selector").val("All");
var query = $(this).val();
$('.col-lg-4').hide();
$(".info-box-text").each(function() {
@ -274,6 +266,70 @@ Wireguard Clients
$(".badge-secondary").filter(':contains("' + query + '")').parent().parent().parent().show();
})
$("#status-selector").on('change', function () {
$('#search-input').val("");
switch ($("#status-selector").val()) {
case "All":
$('.col-lg-4').show();
break;
case "Enabled":
$('.col-lg-4').hide();
$('[id^="paused_"]').each(function () {
if ($(this).css("visibility") === "hidden") {
$(this).parent().parent().show();
}
});
break;
case "Disabled":
$('.col-lg-4').hide();
$('[id^="paused_"]').each(function () {
if ($(this).css("visibility") !== "hidden") {
$(this).parent().parent().show();
}
});
break;
case "Connected":
$('.col-lg-4').hide();
$.ajax({
cache: false,
method: 'GET',
url: '{{.basePath}}/status',
success: function (data) {
const returnedHTML = $(data).find(".table-success").get();
var returnedString = "";
returnedHTML.forEach(entry => returnedString += entry.outerHTML);
$(".fa-key").each(function () {
if (returnedString.indexOf($(this).parent().text().trim()) != -1) {
$(this).closest('.col-lg-4').show();
}
})
}
});
break;
case "Disconnected":
$('.col-lg-4').show();
$.ajax({
cache: false,
method: 'GET',
url: '{{.basePath}}/status',
success: function (data) {
const returnedHTML = $(data).find(".table-success").get();
var returnedString = "";
returnedHTML.forEach(entry => returnedString += entry.outerHTML);
$(".fa-key").each(function () {
if (returnedString.indexOf($(this).parent().text().trim()) != -1) {
$(this).closest('.col-lg-4').hide();
}
})
}
});
break;
default:
$('.col-lg-4').show();
break;
}
});
// modal_pause_client modal event
$("#modal_pause_client").on('show.bs.modal', function (event) {
const button = $(event.relatedTarget);
@ -425,9 +481,7 @@ Wireguard Clients
cache: false,
method: 'GET',
url: '{{.basePath}}/api/client/' + client_id,
data: {
qrCodeIncludeFwMark: include_fwmark
},
data: JSON.stringify(data),
dataType: 'json',
contentType: "application/json",
success: function (resp) {

View file

@ -56,10 +56,10 @@ Global Settings
value="{{if .globalSettings.PersistentKeepalive }}{{ .globalSettings.PersistentKeepalive }}{{end}}">
</div>
<div class="form-group">
<label for="forward_mark">Forward Mark</label>
<input type="text" class="form-control" id="forward_mark"
name="forward_mark" placeholder="Forward Mark"
value="{{ .globalSettings.ForwardMark }}">
<label for="firewall_mark">Firewall Mark</label>
<input type="text" class="form-control" id="firewall_mark"
name="firewall_mark" placeholder="Firewall Mark"
value="{{ .globalSettings.FirewallMark }}">
</div>
<div class="form-group">
<label for="config_file_path">Wireguard Config File Path</label>
@ -100,8 +100,8 @@ Global Settings
until they reach out to other peers themselves. Adding <code>PersistentKeepalive</code>
can ensure that the connection remains open.</dd>
<dd>Leave blank to omit this setting in the Client config.</dd>
<dt>5. Forward Mark</dt>
<dd>Set an <code>fwmark</code> on all packets going out of WireGuard's UDP socket. Default value: <code>0xca6c</code></dd>
<dt>5. Firewall Mark</dt>
<dd>Add a matching <code>fwmark</code> on all packets going out of a WireGuard non-default-route tunnel. Default value: <code>0xca6c</code></dd>
<dt>6. Wireguard Config File Path</dt>
<dd>The path of your Wireguard server config file. Please make sure the parent directory
exists and is writable.</dd>
@ -149,9 +149,9 @@ Global Settings
const dns_servers = $("#dns_servers").val().split(",");
const mtu = $("#mtu").val();
const persistent_keepalive = $("#persistent_keepalive").val();
const forward_mark = $("#forward_mark").val();
const firewall_mark = $("#firewall_mark").val();
const config_file_path = $("#config_file_path").val();
const data = {"endpoint_address": endpoint_address, "dns_servers": dns_servers, "mtu": mtu, "persistent_keepalive": persistent_keepalive, "forward_mark": forward_mark, "config_file_path": config_file_path};
const data = {"endpoint_address": endpoint_address, "dns_servers": dns_servers, "mtu": mtu, "persistent_keepalive": persistent_keepalive, "firewall_mark": firewall_mark, "config_file_path": config_file_path};
$.ajax({
cache: false,
@ -222,7 +222,7 @@ Global Settings
config_file_path: {
required: true
},
forward_mark: {
firewall_mark: {
required: false
}
},

View file

@ -101,7 +101,11 @@
$("#btn_login").click(function () {
const username = $("#username").val();
const password = $("#password").val();
const data = {"username": username, "password": password}
let rememberMe = false;
if ($("#remember").is(':checked')){
rememberMe = true;
}
const data = {"username": username, "password": password, "rememberMe": rememberMe}
$.ajax({
cache: false,

View file

@ -31,7 +31,7 @@ Profile
<div class="form-group">
<label for="username" class="control-label">Username</label>
<input type="text" class="form-control" name="username" id="username"
value="{{ .userInfo.Username }}">
value="">
</div>
<div class="form-group">
<label for="password" class="control-label">Password</label>
@ -55,56 +55,82 @@ Profile
{{ define "bottom_js"}}
<script>
function updateUserInfo() {
const username = $("#username").val();
const password = $("#password").val();
const data = {"username": username, "password": password};
$.ajax({
cache: false,
method: 'POST',
url: '{{.basePath}}/profile',
dataType: 'json',
contentType: "application/json",
data: JSON.stringify(data),
success: function (data) {
toastr.success("Updated admin user information successfully");
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
{
var previous_username;
var admin;
}
$(document).ready(function () {
$.ajax({
cache: false,
method: 'GET',
url: '{{.basePath}}/api/user/{{.baseData.CurrentUser}}',
dataType: 'json',
contentType: "application/json",
success: function (resp) {
const user = resp;
$("#username").val(user.username);
previous_username = user.username;
admin = user.admin;
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
});
});
}
$(document).ready(function () {
$.validator.setDefaults({
submitHandler: function () {
updateUserInfo();
}
function updateUserInfo() {
const username = $("#username").val();
const password = $("#password").val();
const data = {"username": username, "password": password, "previous_username": previous_username, "admin":admin};
$.ajax({
cache: false,
method: 'POST',
url: '{{.basePath}}/update-user',
dataType: 'json',
contentType: "application/json",
data: JSON.stringify(data),
success: function (data) {
toastr.success("Updated user information successfully");
location.reload();
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
});
}
$(document).ready(function () {
$.validator.setDefaults({
submitHandler: function () {
updateUserInfo();
}
});
$("#frm_profile").validate({
rules: {
username: {
required: true
}
},
messages: {
username: {
required: "Please enter a username",
}
},
errorElement: 'span',
errorPlacement: function (error, element) {
error.addClass('invalid-feedback');
element.closest('.form-group').append(error);
},
highlight: function (element, errorClass, validClass) {
$(element).addClass('is-invalid');
},
unhighlight: function (element, errorClass, validClass) {
$(element).removeClass('is-invalid');
}
});
});
$("#frm_profile").validate({
rules: {
username: {
required: true
}
},
messages: {
username: {
required: "Please enter a username",
}
},
errorElement: 'span',
errorPlacement: function (error, element) {
error.addClass('invalid-feedback');
element.closest('.form-group').append(error);
},
highlight: function (element, errorClass, validClass) {
$(element).addClass('is-invalid');
},
unhighlight: function (element, errorClass, validClass) {
$(element).removeClass('is-invalid');
}
});
});
</script>
{{ end }}

View file

@ -0,0 +1,294 @@
{{define "title"}}
Users Settings
{{end}}
{{define "top_css"}}
{{end}}
{{define "username"}}
{{ .username }}
{{end}}
{{define "page_title"}}
Users Settings
{{end}}
{{define "page_content"}}
<section class="content">
<div class="container-fluid">
<div class="row" id="users-list">
</div>
</div>
</section>
<div class="modal fade" id="modal_edit_user">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Edit User</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form name="frm_edit_user" id="frm_edit_user">
<div class="modal-body">
<div class="form-group" style="display:none">
<input type="text" style="display:none" class="form-control" id="_previous_user_name"
name="_previous_user_name">
</div>
<div class="form-group">
<label for="_user_name" class="control-label">Name</label>
<input type="text" class="form-control" id="_user_name" name="_user_name">
</div>
<div class="form-group">
<label for="_user_password" class="control-label">Password</label>
<input type="text" class="form-control" id="_user_password" name="_user_password" value=""
placeholder="Leave empty to keep the password unchanged">
</div>
<div class="form-group">
<div class="icheck-primary d-inline">
<input type="checkbox" id="_admin">
<label for="_admin">
Admin
</label>
</div>
</div>
</div>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success">Save</button>
</div>
</form>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div class="modal fade" id="modal_remove_user">
<div class="modal-dialog">
<div class="modal-content bg-danger">
<div class="modal-header">
<h4 class="modal-title">Remove</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-outline-dark" id="remove_user_confirm">Apply</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
{{end}}
{{define "bottom_js"}}
<script>
function populateUsersList() {
$.ajax({
cache: false,
method: 'GET',
url: '{{.basePath}}/getusers',
dataType: 'json',
contentType: "application/json",
success: function (data) {
renderUserList(data);
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
});
}
</script>
<script>
// load user list
$(document).ready(function () {
populateUsersList();
let newUserHtml = '<div class="col-sm-2 offset-md-4" style=" text-align: right;">' +
'<button style="" id="btn_new_user" type="button" class="btn btn-outline-primary btn-sm" ' +
'data-toggle="modal" data-target="#modal_edit_user" data-username="">' +
'<i class="nav-icon fas fa-plus"></i> New User</button></div>';
$('h1').parents(".row").append(newUserHtml);
})
// modal_remove_user modal event
$("#modal_remove_user").on('show.bs.modal', function (event) {
const button = $(event.relatedTarget);
const user_name = button.data('username');
const modal = $(this);
modal.find('.modal-body').text("You are about to remove user " + user_name);
modal.find('#remove_user_confirm').val(user_name);
})
// remove_user_confirm button event
$(document).ready(function () {
$("#remove_user_confirm").click(function () {
const user_name = $(this).val();
const data = {"username": user_name};
$.ajax({
cache: false,
method: 'POST',
url: '{{.basePath}}/remove-user',
dataType: 'json',
contentType: "application/json",
data: JSON.stringify(data),
success: function (data) {
$("#modal_remove_user").modal('hide');
toastr.success('Removed user successfully');
const divElement = document.getElementById('user_' + user_name);
divElement.style.display = "none";
location.reload()
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
});
});
});
// Edit user modal event
$(document).ready(function () {
$("#modal_edit_user").on('show.bs.modal', function (event) {
let modal = $(this);
const button = $(event.relatedTarget);
const user_name = button.data('username');
// update user modal data
if (user_name !== "") {
$.ajax({
cache: false,
method: 'GET',
url: '{{.basePath}}/api/user/' + user_name,
dataType: 'json',
contentType: "application/json",
success: function (resp) {
const user = resp;
modal.find(".modal-title").text("Edit user " + user.username);
modal.find("#_user_name").val(user.username);
modal.find("#_previous_user_name").val(user.username);
modal.find("#_user_password").val("");
modal.find("#_user_password").prop("placeholder", "Leave empty to keep the password unchanged")
modal.find("#_admin").prop("checked", user.admin);
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
});
} else {
modal.find(".modal-title").text("Add new user");
modal.find("#_user_name").val("");
modal.find("#_previous_user_name").val("");
modal.find("#_user_password").val("");
modal.find("#_user_password").prop("placeholder", "")
modal.find("#_admin").prop("checked", false);
}
});
});
function updateUserInfo() {
const username = $("#_user_name").val();
const previous_username = $("#_previous_user_name").val();
const password = $("#_user_password").val();
let admin = false;
if ($("#_admin").is(':checked')) {
admin = true;
}
const data = {
"username": username,
"password": password,
"previous_username": previous_username,
"admin": admin
};
if (previous_username !== "") {
$.ajax({
cache: false,
method: 'POST',
url: '{{.basePath}}/update-user',
dataType: 'json',
contentType: "application/json",
data: JSON.stringify(data),
success: function (data) {
toastr.success("Updated user information successfully");
location.reload();
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
});
} else {
$.ajax({
cache: false,
method: 'POST',
url: '{{.basePath}}/create-user',
dataType: 'json',
contentType: "application/json",
data: JSON.stringify(data),
success: function (data) {
toastr.success("Created user successfully");
location.reload();
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);
toastr.error(responseJson['message']);
}
});
}
}
$(document).ready(function () {
$.validator.setDefaults({
submitHandler: function (form) {
updateUserInfo();
}
});
// Edit user form validation
$("#frm_edit_user").validate({
rules: {
_user_name: {
required: true
},
_user_password: {
required: function () {
return $("#_previous_user_name").val() === "";
}
},
},
messages: {
_user_name: {
required: "Please enter a username"
},
_user_password: {
required: "Please input a password"
},
},
errorElement: 'span',
errorPlacement: function (error, element) {
error.addClass('invalid-feedback');
element.closest('.form-group').append(error);
},
highlight: function (element, errorClass, validClass) {
$(element).addClass('is-invalid');
},
unhighlight: function (element, errorClass, validClass) {
$(element).removeClass('is-invalid');
}
});
//
});
</script>
{{end}}