Add server creation UI + user-server access assignment (step 3)
New admin-only page at /servers-settings (templates/servers.html) lists servers and creates new ones via POST /servers (ID/name/interface/ addresses/port, key pair generated server-side). Nav gets a "Servers" link. templates/users_settings.html gains a multi-select "Server Access" field wired to the server_ids support added to create-user/update-user in the previous commit, so admins can now actually assign non-admin users to specific servers through the UI.
This commit is contained in:
@@ -139,6 +139,14 @@
|
||||
|
||||
|
||||
<li class="nav-header">SETTINGS</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/servers-settings" class="nav-link {{if eq .baseData.Active "servers" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-server"></i>
|
||||
<p>
|
||||
Servers
|
||||
</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>
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
{{define "title"}}
|
||||
Servers
|
||||
{{end}}
|
||||
|
||||
{{define "top_css"}}
|
||||
{{end}}
|
||||
|
||||
{{define "username"}}
|
||||
{{ .username }}
|
||||
{{end}}
|
||||
|
||||
{{define "page_title"}}
|
||||
Servers
|
||||
{{end}}
|
||||
|
||||
{{define "page_content"}}
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row" id="servers-list">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal fade" id="modal_new_server">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Add new server</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form name="frm_new_server" id="frm_new_server">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="_server_id" class="control-label">ID</label>
|
||||
<input type="text" class="form-control" id="_server_id" name="_server_id"
|
||||
placeholder="e.g. wg-home">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_server_name" class="control-label">Name</label>
|
||||
<input type="text" class="form-control" id="_server_name" name="_server_name"
|
||||
placeholder="e.g. WGhome">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_server_interface" class="control-label">Interface</label>
|
||||
<input type="text" class="form-control" id="_server_interface" name="_server_interface"
|
||||
placeholder="e.g. wg-home">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_server_addresses" class="control-label">Addresses</label>
|
||||
<input type="text" class="form-control" id="_server_addresses" name="_server_addresses"
|
||||
placeholder="e.g. 10.20.22.0/24, fd00::/64">
|
||||
<small class="form-text text-muted">Comma-separated list of address ranges.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_server_listen_port" class="control-label">Listen Port</label>
|
||||
<input type="text" class="form-control" id="_server_listen_port" name="_server_listen_port"
|
||||
placeholder="e.g. 51822">
|
||||
</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 -->
|
||||
{{end}}
|
||||
|
||||
{{define "bottom_js"}}
|
||||
<script>
|
||||
function renderServersList(data) {
|
||||
$.each(data, function (index, obj) {
|
||||
const addresses = (obj.Interface && obj.Interface.addresses) ? obj.Interface.addresses.join(", ") : "";
|
||||
const listenPort = obj.Interface ? obj.Interface.listen_port : "";
|
||||
const interfaceName = obj.Interface ? obj.Interface.name : "";
|
||||
|
||||
let html = `<div class="col-sm-6 col-md-6 col-lg-4" id="server_${obj.id}">
|
||||
<div class="info-box">
|
||||
<div class="info-box-content">
|
||||
<div class="btn-group">
|
||||
<a href="{{.basePath}}/servers/${obj.id}/clients" class="btn btn-outline-primary btn-sm">Manage clients</a>
|
||||
</div>
|
||||
<hr>
|
||||
<span class="info-box-text"><i class="fas fa-server"></i> ${obj.name}</span>
|
||||
<span class="info-box-text"><i class="fas fa-fingerprint"></i> ID: ${obj.id}</span>
|
||||
<span class="info-box-text"><i class="fas fa-ethernet"></i> Interface: ${interfaceName}</span>
|
||||
<span class="info-box-text"><i class="fas fa-plug"></i> Listen Port: ${listenPort}</span>
|
||||
<span class="info-box-text"><i class="fas fa-map-marker-alt"></i> Addresses: ${addresses}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
$('#servers-list').append(html);
|
||||
});
|
||||
}
|
||||
|
||||
function populateServersList() {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/servers',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
renderServersList(data);
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// load server list
|
||||
$(document).ready(function () {
|
||||
populateServersList();
|
||||
let newServerHtml = '<div class="col-sm-2 offset-md-4" style=" text-align: right;">' +
|
||||
'<button style="" id="btn_new_server" type="button" class="btn btn-outline-primary btn-sm" ' +
|
||||
'data-toggle="modal" data-target="#modal_new_server">' +
|
||||
'<i class="nav-icon fas fa-plus"></i> New Server</button></div>';
|
||||
$('h1').parents(".row").append(newServerHtml);
|
||||
})
|
||||
|
||||
// New server modal event: reset the form each time it is opened
|
||||
$(document).ready(function () {
|
||||
$("#modal_new_server").on('show.bs.modal', function (event) {
|
||||
let modal = $(this);
|
||||
modal.find("#_server_id").val("");
|
||||
modal.find("#_server_name").val("");
|
||||
modal.find("#_server_interface").val("");
|
||||
modal.find("#_server_addresses").val("");
|
||||
modal.find("#_server_listen_port").val("");
|
||||
});
|
||||
});
|
||||
|
||||
function submitNewServer() {
|
||||
const id = $("#_server_id").val();
|
||||
const name = $("#_server_name").val();
|
||||
const iface = $("#_server_interface").val();
|
||||
const addresses = $("#_server_addresses").val().split(",").map(function (a) {
|
||||
return a.trim();
|
||||
}).filter(function (a) {
|
||||
return a !== "";
|
||||
});
|
||||
const listen_port = parseInt($("#_server_listen_port").val(), 10);
|
||||
|
||||
const data = {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"interface": iface,
|
||||
"addresses": addresses,
|
||||
"listen_port": listen_port
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/servers',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function (data) {
|
||||
$("#modal_new_server").modal('hide');
|
||||
toastr.success("Created server 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) {
|
||||
submitNewServer();
|
||||
}
|
||||
});
|
||||
$("#frm_new_server").validate({
|
||||
rules: {
|
||||
_server_id: {
|
||||
required: true
|
||||
},
|
||||
_server_name: {
|
||||
required: true
|
||||
},
|
||||
_server_interface: {
|
||||
required: true
|
||||
},
|
||||
_server_listen_port: {
|
||||
required: true,
|
||||
digits: true,
|
||||
range: [1, 65535]
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
_server_id: {
|
||||
required: "Please enter a server ID"
|
||||
},
|
||||
_server_name: {
|
||||
required: "Please enter a server name"
|
||||
},
|
||||
_server_interface: {
|
||||
required: "Please enter an interface name"
|
||||
},
|
||||
_server_listen_port: {
|
||||
required: "Please enter a port",
|
||||
digits: "Port must be an integer",
|
||||
range: "Port must be in range 1..65535"
|
||||
}
|
||||
},
|
||||
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}}
|
||||
@@ -53,6 +53,12 @@ Users Settings
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_server_ids" class="control-label">Server Access</label>
|
||||
<select multiple class="form-control" id="_server_ids" name="_server_ids">
|
||||
</select>
|
||||
<small class="form-text text-muted">Servers this user (if non-admin) may access. Admins always have access to all servers.</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
@@ -163,6 +169,32 @@ Users Settings
|
||||
const button = $(event.relatedTarget);
|
||||
const user_name = button.data('username');
|
||||
|
||||
// populate the server access select with the current list of servers
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/servers',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function (servers) {
|
||||
const select = modal.find("#_server_ids");
|
||||
select.empty();
|
||||
$.each(servers, function (index, srv) {
|
||||
select.append($('<option>').val(srv.id).text(srv.name + " (" + srv.id + ")"));
|
||||
});
|
||||
|
||||
// if editing an existing user, pre-select their granted servers now that
|
||||
// the option list exists
|
||||
if (user_name !== "") {
|
||||
select.val(select.data('preselect') || []);
|
||||
}
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
|
||||
// update user modal data
|
||||
if (user_name !== "") {
|
||||
$.ajax({
|
||||
@@ -180,6 +212,10 @@ Users Settings
|
||||
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);
|
||||
// remember the granted server ids so the select can pre-select them
|
||||
// once its options have been populated (see the servers ajax above)
|
||||
modal.find("#_server_ids").data('preselect', user.server_ids || []);
|
||||
modal.find("#_server_ids").val(user.server_ids || []);
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
@@ -193,6 +229,7 @@ Users Settings
|
||||
modal.find("#_user_password").val("");
|
||||
modal.find("#_user_password").prop("placeholder", "")
|
||||
modal.find("#_admin").prop("checked", false);
|
||||
modal.find("#_server_ids").data('preselect', []);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -205,11 +242,13 @@ Users Settings
|
||||
if ($("#_admin").is(':checked')) {
|
||||
admin = true;
|
||||
}
|
||||
const server_ids = $("#_server_ids").val() || [];
|
||||
const data = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
"previous_username": previous_username,
|
||||
"admin": admin
|
||||
"admin": admin,
|
||||
"server_ids": server_ids
|
||||
};
|
||||
|
||||
if (previous_username !== "") {
|
||||
|
||||
Reference in New Issue
Block a user