Erik Thuning 04bbe17b48 Added a VPN status banner
The banner calls ip.dsv.su.se and uses the returned information to determine
whether the client is using a VPN connection.
2025-05-14 11:44:02 +02:00

307 lines
12 KiB
JavaScript

'use strict';
(function() {
window.addEventListener('DOMContentLoaded', (event) => {
setup_page();
vpn_status();
});
function vpn_status() {
const wrapper = document.querySelector('vpnstatus');
const output = wrapper.querySelector('.result');
fetch('https://ip.dsv.su.se',
{headers: {'Accept': 'application/json'}})
.then(response => {
if (!response.ok) {
throw new Error("API request failed: " + response.status);
}
return response.json();
})
.then(data => {
const hostname = window.location.hostname.split('.')[0];
if(data.vpn === hostname) {
output.textContent = "Using VPN";
wrapper.classList.add('active');
} else {
output.textContent = "Not using VPN";
wrapper.classList.add('inactive');
}
})
.catch(err => {
const out = "Error when checking VPN status: " + err.message;
output.textContent = out;
wrapper.classList.add('error');
});
}
function close_modal(node) {
let current = node;
while(current.nodeName != 'BACKDROP') {
current = current.parentNode;
if(current.nodeName === 'HTML') {
console.error("can't find modal parent");
return;
}
}
current.parentNode.removeChild(current);
}
function deny_access() {
const vpnstatus = document.querySelector('vpnstatus');
vpnstatus.replaceWith('');
const topbox = document.querySelector('topbox');
while(topbox.firstChild) {
topbox.removeChild(topbox.lastChild);
}
const placeholder = document.querySelector('placeholder');
placeholder.replaceWith('');
const template = document.querySelector('template#access-denied')
.content.cloneNode(true);
topbox.appendChild(template);
document.querySelector('body').removeAttribute('style');
}
function display_create_form() {
const template = document.querySelector('template#create-form')
.content.cloneNode(true);
const form = template.querySelector('form');
form.addEventListener('submit', (event) => {
event.preventDefault();
const id = crypto.randomUUID();
const name = form.name.value;
if(!name) {
return;
}
const description = form.description.value.trim();
make_api_request('POST',
'/configs/' + id + '/create',
{'name': name,
'description': description})
.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, 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) => {
config.id = 'config-' + config_id;
config.querySelector('name').textContent = data.name;
config.querySelector('description').textContent =
data.description;
config.querySelector('data').textContent = data.data;
const qr = config.querySelector('.qr');
qr.innerHTML = data.qrcode;
const svg = qr.querySelector('svg');
svg.setAttribute('shape-rendering', 'crisp-edges');
svg.setAttribute('aria-label', 'QR code');
const expires = config.querySelector('expires');
if(data.expires) {
expires.textContent = data.expires;
} else {
config.removeChild(expires);
}
const link = config.querySelector('.conffile');
link.setAttribute('href',
dlprefix + encodeURIComponent(data.data));
link.setAttribute('download', data.name + '.conf');
config.querySelector('button.edit')
.addEventListener('click', (event) => {
display_edit_form(config_id, data);
});
config.querySelector('button.download')
.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);
}
}
update_create_button();
});
});
}
function display_edit_form(config_id, config) {
const template = document.querySelector('template#update-form')
.content.cloneNode(true);
const form = template.querySelector('form');
form.name.value = config.name;
form.description.value = config.description;
form.addEventListener('submit', (event) => {
event.preventDefault();
make_api_request('POST',
'/configs/' + config_id + '/update',
{'name': form.name.value,
'description': form.description.value.trim()})
.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);
});
});
const delete_button = form.querySelector('button.delete');
delete_button.addEventListener('click', (event) => {
if(!window.confirm('Are you sure you want to delete this client?')) {
return;
}
make_api_request('POST',
'/configs/' + config_id + '/delete')
.then((response) => {
document.querySelector('#config-'+config_id).remove();
update_create_button();
close_modal(form);
})
.catch((exception) => {
console.error('deletion failed for: '+config_id,
exception);
});
});
display_modal(template, form.name);
}
function display_modal(fragment, focus_element) {
const modal = document.querySelector('template#modal')
.content.cloneNode(true);
modal.querySelector('wrapper').appendChild(fragment);
const backdrop = modal.querySelector('backdrop');
const cancel = modal.querySelector('button.cancel');
backdrop.addEventListener('click', (event) => {
if(event.target === backdrop
|| event.target === cancel) {
close_modal(backdrop);
}
});
document.querySelector('body').appendChild(modal);
focus_element.focus();
}
function get_cookies() {
var out = new Object();
const cookies = document.cookie.split('; ');
cookies.forEach((cookie) => {
const temp = cookie.split('=');
const name = temp[0];
const value = temp.slice(1).join('=');
out[name] = value;
});
return out;
}
function login() {
const current_path = window.location.pathname;
return window.location.replace('/api/login?return='
+ current_path);
}
async function make_api_request(method, path, body) {
const data = {'method': method,
'headers': {'Content-Type': 'application/json'}};
if(method != 'GET') {
data['body'] = JSON.stringify(body);
}
const request = new Request('/api' + path, data);
const response = await fetch(request);
if(response.status === 403) {
const cookies = get_cookies();
const access = cookies['access'];
if(access === 'denied') {
throw new Error('access denied');
}
login();
}
return response.json();
}
function update_create_button() {
const visible_configs = document.querySelectorAll('configs > config');
const max_clients = sessionStorage.getItem('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/');
document.querySelector('body').removeAttribute('style');
const cookies = get_cookies();
const settings = JSON.parse(atob(cookies['server_settings']));
sessionStorage.setItem('max_clients', settings['client_limit']);
document.querySelector('head > title')
.textContent = settings['site_name'];
if('topbox_content' in settings) {
fetch(settings['topbox_content'])
.then((response) => response.text())
.then((data) => {
const template = document.createElement('template');
template.innerHTML = data;
document.querySelector('topbox > details')
.replaceWith(template.content.cloneNode(true));
});
}
document.querySelector('user#banner-userid')
.textContent = cookies['username'];
document.querySelector('button#create-config')
.addEventListener('click', (event) => {
display_create_form();
});
await display_configs(...configs);
} catch(e) {
if(e.message === 'access denied') {
deny_access();
return;
}
throw e;
}
}
})();