Implemented handling of client limits, and some general tweaks
- The create button is now disabled when reaching the client limit - The client list is now always sorted by case-insensitive config name - Focus moves to the form when opening a dialog - Fixed some inconsistent use of snake_case vs perlCase
This commit is contained in:
parent
d8dcbc6d6b
commit
49abf8ad21
@ -17,6 +17,7 @@ public_paths = [login_path, callback_path]
|
||||
user_cookie = 'username'
|
||||
token_cookie = 'token'
|
||||
access_cookie = 'access'
|
||||
limit_cookie = 'max_clients'
|
||||
permitted_format = re.compile('^[A-Za-z0-9-]+$')
|
||||
|
||||
config = ConfigParser()
|
||||
@ -62,6 +63,10 @@ def setup() -> None:
|
||||
|
||||
@app.after_request
|
||||
def reload(response: Response) -> Response:
|
||||
response.set_cookie(limit_cookie,
|
||||
config['wireguard']['user_client_limit'],
|
||||
secure=True,
|
||||
samesite='Strict')
|
||||
if app.wg.user_name:
|
||||
response.set_cookie(user_cookie,
|
||||
app.wg.user_name,
|
||||
@ -122,7 +127,7 @@ def create_config(config_id: str) -> dict:
|
||||
return fail('Id already in use')
|
||||
except ClientLimitError as e:
|
||||
return fail(e.message)
|
||||
return get_config(config_id)
|
||||
return {'result': 'success'}
|
||||
|
||||
@app.route('/configs/<config_id>/update', methods=['POST'])
|
||||
def update_config(config_id: str) -> dict:
|
||||
|
@ -107,6 +107,9 @@ def run_wg(*args, input: str=None):
|
||||
return result.stdout
|
||||
|
||||
def run_command(command, *args) -> None:
|
||||
# Temporarily disabling all external calls
|
||||
return
|
||||
|
||||
# The command must be called on an absolute path so that it's possible
|
||||
# to set up a safe sudoers rule for it.
|
||||
command_path = Path().joinpath('commands.sh')
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
(function() {
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
setupPage();
|
||||
setup_page();
|
||||
});
|
||||
|
||||
function close_modal(node) {
|
||||
@ -44,34 +44,45 @@
|
||||
'/configs/' + id + '/create',
|
||||
{'name': name,
|
||||
'description': description})
|
||||
.then((response) => {display_configs(id)})
|
||||
.then((response) => {close_modal(form)})
|
||||
.then((response) => {
|
||||
if(response.result == 'success') {
|
||||
display_configs(id);
|
||||
close_modal(form);
|
||||
} else {
|
||||
throw new Error(response.reason);
|
||||
}
|
||||
})
|
||||
.catch((exception) => {
|
||||
console.error('creation failed for: '+id, exception);
|
||||
});
|
||||
});
|
||||
display_modal(template);
|
||||
display_modal(template, form.name);
|
||||
}
|
||||
|
||||
function display_configs(...config_ids) {
|
||||
const configs_parent = document.querySelector('configs');
|
||||
const dlprefix = 'data:text/plain;charset:utf-8,';
|
||||
config_ids.forEach((config_id) => {
|
||||
const old = configs_parent.querySelector('#config-'+config_id);
|
||||
if(old) {
|
||||
old.parentNode.removeChild(old);
|
||||
}
|
||||
const template = document.querySelector('template#display-config')
|
||||
.content.cloneNode(true);
|
||||
const config = template.querySelector('config');
|
||||
make_api_request('GET', '/configs/' + config_id)
|
||||
.then((data) => {
|
||||
const qr = QRCode({msg: data.data,
|
||||
dim: 280,
|
||||
pad: 0,
|
||||
ecl: 'M'});
|
||||
qr.setAttribute('aria-label', 'QR code');
|
||||
config.id = 'config-' + config_id;
|
||||
config.querySelector('name').textContent = data.name;
|
||||
config.querySelector('date').textContent = data.created;
|
||||
config.querySelector('description').textContent = data.description;
|
||||
config.querySelector('data').textContent = data.data;
|
||||
config.querySelector('qrcode').replaceWith(
|
||||
QRCode({msg: data.data,
|
||||
dim: 280,
|
||||
pad: 0,
|
||||
ecl: 'M'}));
|
||||
config.querySelector('qrcode').replaceWith(qr);
|
||||
const link = config.querySelector('.conffile');
|
||||
link.setAttribute('href',
|
||||
dlprefix + encodeURIComponent(data.data));
|
||||
@ -84,14 +95,22 @@
|
||||
.addEventListener('click', (event) => {
|
||||
link.click();
|
||||
});
|
||||
const config_children = configs_parent.children;
|
||||
for(let i = 0; i < config_children.length; i++) {
|
||||
const child = config_children[i];
|
||||
if(child.nodeName === 'PLACEHOLDER') {
|
||||
configs_parent.insertBefore(template, child);
|
||||
break;
|
||||
}
|
||||
const name = child.querySelector('name').textContent;
|
||||
if(data.name.toLowerCase() < name.toLowerCase()) {
|
||||
configs_parent.insertBefore(template, child);
|
||||
}
|
||||
}
|
||||
});
|
||||
const old = configs_parent.querySelector('#config-'+config_id);
|
||||
if(old) {
|
||||
old.replaceWith(template);
|
||||
} else {
|
||||
configs_parent.appendChild(template);
|
||||
}
|
||||
});
|
||||
|
||||
update_create_button();
|
||||
}
|
||||
|
||||
function display_edit_form(config_id, config) {
|
||||
@ -102,15 +121,18 @@
|
||||
form.description.value = config.description;
|
||||
form.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
const save_button = form.querySelector('button.save');
|
||||
save_button.addEventListener('click', (event) => {
|
||||
make_api_request('POST',
|
||||
'/configs/' + config_id + '/update',
|
||||
{'name': form.name.value,
|
||||
'description': form.description.value.trim()})
|
||||
.then((response) => {display_configs(config_id)})
|
||||
.then((response) => {close_modal(form)})
|
||||
.then((response) => {
|
||||
if(response.result == 'success') {
|
||||
display_configs(config_id);
|
||||
close_modal(form);
|
||||
} else {
|
||||
throw new Error(response.reason);
|
||||
}
|
||||
})
|
||||
.catch((exception) => {
|
||||
console.error('update failed for: '+config_id,
|
||||
exception);
|
||||
@ -125,17 +147,18 @@
|
||||
'/configs/' + config_id + '/delete')
|
||||
.then((response) => {
|
||||
document.querySelector('#config-'+config_id).remove();
|
||||
update_create_button();
|
||||
close_modal(form);
|
||||
})
|
||||
.then((response) => {close_modal(form)})
|
||||
.catch((exception) => {
|
||||
console.error('deletion failed for: '+config_id,
|
||||
exception);
|
||||
});
|
||||
});
|
||||
display_modal(template);
|
||||
display_modal(template, form.name);
|
||||
}
|
||||
|
||||
function display_modal(fragment) {
|
||||
function display_modal(fragment, focus_element) {
|
||||
const modal = document.querySelector('template#modal')
|
||||
.content.cloneNode(true);
|
||||
modal.querySelector('wrapper').appendChild(fragment);
|
||||
@ -150,9 +173,10 @@
|
||||
});
|
||||
|
||||
document.querySelector('body').appendChild(modal);
|
||||
focus_element.focus();
|
||||
}
|
||||
|
||||
function getCookies() {
|
||||
function get_cookies() {
|
||||
var out = new Object();
|
||||
const cookies = document.cookie.split('; ');
|
||||
cookies.forEach((cookie) => {
|
||||
@ -179,7 +203,7 @@
|
||||
const request = new Request('/api' + path, data);
|
||||
const response = await fetch(request);
|
||||
if(response.status === 403) {
|
||||
const cookies = getCookies();
|
||||
const cookies = get_cookies();
|
||||
const access = cookies['access'];
|
||||
if(access === 'denied') {
|
||||
throw new Error('access denied');
|
||||
@ -189,10 +213,26 @@
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function setupPage(route) {
|
||||
function update_create_button() {
|
||||
const visible_configs = document.querySelectorAll('configs > config');
|
||||
const cookies = get_cookies();
|
||||
const max_clients = cookies['max_clients'];
|
||||
|
||||
let button_disabled = false;
|
||||
let button_message = "Add a client";
|
||||
if(max_clients > 0 && visible_configs.length >= max_clients) {
|
||||
button_disabled = true;
|
||||
button_message = "Limit of "+max_clients+" clients reached";
|
||||
}
|
||||
const button = document.querySelector('button#create-config');
|
||||
button.disabled = button_disabled;
|
||||
button.innerHTML = button_message;
|
||||
}
|
||||
|
||||
async function setup_page(route) {
|
||||
try {
|
||||
const configs = await make_api_request('GET', '/configs/');
|
||||
const cookies = getCookies();
|
||||
const cookies = get_cookies();
|
||||
document.querySelector('user#banner-userid')
|
||||
.textContent = cookies['username'];
|
||||
document.querySelector('button#create-config')
|
||||
|
@ -231,6 +231,12 @@ button:hover {
|
||||
background-color: #33587F;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
color: #000000;
|
||||
background-color: #BABABA;
|
||||
border: 2px solid #DADADA;
|
||||
}
|
||||
|
||||
button#create-config {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user