'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; } } })();