const assetsPath = "../assets/"; const api = new APIClient(); const toastContainer = $('.toast-container'); const onlineUsers = new Set(); let renderTableCell = (function () { function renderTitle(title) { return title .replace('_', ' ') .split(" ") .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); } function renderSwitch(data, disable=false) { let enabled = data == true; let active = enabled ? ' active' : ''; let disabled = disable ? 'disabled' : ''; return ' '; } function renderName(full) { var $name = full['name'], $lastSeen = full['lastHandshake'].replace('T', ' ') $userId = full['id']; // Avatar badge var stateNum = Math.floor(Math.random() * 6); var states = ['success', 'danger', 'warning', 'info', 'dark', 'primary', 'secondary']; var $state = states[stateNum], $name = full['name'], $initials = ($name || '').match(/\b\w/g)?.join('').toUpperCase() || ''; let online = 'offline'; // online check if ($lastSeen.length == 8) { const time = new Date(`2000-01-01T${$lastSeen}`); const comparison = new Date(`2000-01-01T00:03:00`); if (time < comparison) { online = 'online'; onlineUsers.add($userId); }; } // end online check let $output = '
' + $initials + '
'; // Creates full output for row var $row_output = '
' + '
' + '
' + $output + '
' + '
' + '
' + '' + $name + '' + '' + $lastSeen + '' + '
' + '
'; return $row_output; } function renderTraffic(full) { let used = convertByteSize(full['trafficUsed']); let total = full['traffic'] == 0 ? '∞' : `${full['traffic']}GB`; let output = `${used} / ${total}`; return '' + output + ''; } function renderExpire(data) { return '' + (data.toLowerCase() != 'unlimited' ? data.replace('T', ' ').slice(0, -3) : data) + ''; } return { renderTitle, renderName, renderSwitch, renderTraffic, renderExpire } })(); $(function() { 'use strict'; var dt_basic_table = $('.datatables-basic'); // DataTable with buttons // -------------------------------------------------------------------- if (dt_basic_table.length) { try { var dt_basic = dt_basic_table.DataTable({ ajax: function(data, callback, settings) { settings.sAjaxDataProp = ''; api.users.getAll() .then(function(response) { callback(response); }) .catch(function(error) { console.error(error); callback([]); }); }, columns: [ { data: '' }, { data: 'isEnabled' }, { data: 'name' }, { data: 'interface' }, { data: 'address'}, { data: 'traffic' }, { data: 'expire' }, { data: 'isDifferent' } ], columnDefs: [ { // For Responsive targets: 0, className: 'dtr-control', orderable: false, responsivePriority: 2, searchable: false, render: function(data, type, full, meta) { return ''; } }, { // Enablity Switch targets: 1, orderable: false, searchable: false, render: function(data, type, full, meta) { return renderTableCell.renderSwitch(data); } }, { // Name and Online status targets: 2, responsivePriority: 1, render: function(data, type, full, meta) { return renderTableCell.renderName(full); } }, { // Interface responsivePriority: 4, targets: 3 }, { // Allowed Address responsivePriority: 3, targets: 4 }, { // Traffic targets: 5, render: function(data, type, full, meta) { return renderTableCell.renderTraffic(full); } }, { // Expire targets: 6, render: function(data, type, full, meta) { return renderTableCell.renderExpire(data); } }, { // Actions targets: -1, // Last title: 'Actions', orderable: false, searchable: false, className: 'action-buttons', render: function(data, type, full, meta) { let dlBtns = data ? '' : '' + ''; return ( '' + dlBtns + '
' + '' + '' + '
' ); } } ], order: [[2, 'desc']], dom: '<"card-header"<"dt-action-buttons text-end"B>><"d-flex justify-content-between align-items-center row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>t<"d-flex justify-content-between row"<"col-sm-12 col-md-6"i><"col-sm-12 col-md-6"p>>', displayLength: 10, lengthMenu: [5, 10, 25, 50, 75, 100], buttons: { dom: { button: { tag: 'button', className: 'btn' } }, buttons: [ { extend: 'collection', className: 'btn-primary dropdown-toggle me-2', text: 'Export', buttons: [ { extend: 'copy', text: 'Copy', className: 'dropdown-item' }, { extend: 'csv', text: 'CSV', className: 'dropdown-item' }, { text: 'JSON', className: 'dropdown-item', action: function ( e, dt, button, config ) { //let data = dt.buttons.exportData(); let array = []; let data = dt.rows().data(); $.each(data, function(i) { array.push(data[i]); }); $.fn.dataTable.fileSave( new Blob([JSON.stringify(array, null, 2)]), 'wg-peers.json' ); } }/*, { text: 'Backup', className: 'dropdown-item' }*/ ] }, { text: ' Add New User', className: 'btn-success create-new', attr: { "data-bs-toggle": "offcanvas", "data-bs-target": "#offcanvasAddUser" } } ] }, responsive: { details: { display: $.fn.dataTable.Responsive.display.modal({ header: function(row) { var data = row.data(); return 'Details of ' + data['name']; } }), type: 'column', renderer: function(api, rowIdx, columns) { var data = $.map(columns, function(col, i) { return col.title !== '' // ? Do not show row in modal popup if title is blank (for check box) ? '' + '' + col.title + ':' + ' ' + '' + col.data + '' + '' : ''; }).join(''); let output = data ? $('').append(data) : false; return output; } } }, language: { "emptyTable": "No users found" }, initComplete: function(settings, json) { const result = Object.values(json).reduce( (acc, currentItem) => { acc.totalItems++; if (currentItem.isEnabled) { acc.enabledItems++; } return acc; }, { totalItems: 0, enabledItems: 0 } ); const totalItems = result.totalItems; const enabledItems = result.enabledItems; $('#users-total').text(totalItems); $('#users-active').text(enabledItems); $('#users-online').text(onlineUsers.size); dt_basic.on('click', 'a.item-details', function (e) { let tr = e.target.closest('tr'); let row = dt_basic.row(tr) let rowData = row.data(); var data = $.map(rowData, function(value, key) { if (key !== '') // ? Do not show row in modal popup if title is blank (for check box) { let tdValue = ''; switch (key) { case 'enabled': tdValue = renderTableCell.renderSwitch(value, true); break; case 'name': tdValue = renderTableCell.renderName(rowData); break; case 'traffic': tdValue = renderTableCell.renderTraffic(rowData); break; case 'expire': tdValue = renderTableCell.renderExpire(value); break; default: tdValue = value; break; } return '' + ' ' + '' + '' } else { return ''; } }).join(''); let body = data ? $('
' + renderTableCell.renderTitle(key) + '
' + tdValue + '
').append(data) : false; $('#DetailsModal .modal-body').html(body); $('#DetailsModal .modal-title').html('Details of ' + row.data()["name"].toUpperCase()); const detailsModal = new bootstrap.Modal('#DetailsModal'); detailsModal.show(); }); dt_basic.on('click', 'a.item-qr', function (e) { let tr = e.target.closest('tr'); let row = dt_basic.row(tr); let rowData = row.data(); let id = rowData['id']; let username = rowData['name']; $('#QRModalLabel').text(username + "'s QR"); api.users.qr(id).then((qrData) => { console.log(qrData); $('#qr-src').attr('src', `data:image/png;base64,${qrData}`); }); }); dt_basic.on('click', 'a.item-download', function (e) { let tr = e.target.closest('tr'); let row = dt_basic.row(tr); let rowData = row.data(); let id = rowData['id']; console.log(`DL: ${id}`); api.users.download(id).then((dlLink) => { console.log(dlLink); window.location = dlLink; }); }); dt_basic.on('click', 'a.item-edit', function (e) { let tr = e.target.closest('tr'); let row = dt_basic.row(tr) let rowData = row.data(); $.map(rowData, function(value, key) { key = key.replace('_', '-'); let field = $('#offcanvasEditUser #edit-' + key); if (field) { switch (key) { case 'name': $('#edit-username').val(value); break; case 'address': $('#edit-allowed-address').val(value.replace('/32', '')); break; case 'publicKey': case 'privateKey': case 'presharedKey': field.val(''); break; case 'expire': if (value != 'Unlimited') $("#edit-expire").flatpickr({ enableTime: true, altInput: true, altFormat: "Y-m-d H:i", dateFormat: "Z", defaultDate: value.replace('T', ' ').slice(0, -3) }); else { $("#edit-expire").flatpickr({ enableTime: true, altInput: true, altFormat: "Y-m-d H:i", dateFormat: "Z" }).clear(); } break; case 'traffic': if (value == 0) field.val(value); else field.val(value); break; default: field.val(value); break; } } }); }); dt_basic.on('click', 'a.item-sync', function (e) { let tr = e.target.closest('tr'); let row = dt_basic.row(tr) let rowData = row.data(); $.map(rowData, function(value, key) { key = key.replace('_', '-'); let field = $('#offcanvasSyncUser #sync-' + key); if (field) { switch (key) { case 'name': $('#sync-username').val(value); break; case 'publicKey': $('#sync-private-key').val(value); break; case 'privateKey': $('#sync-public-key').val(value); break; default: console.log(`${key} => ${value}`); field.val(value); break; } } }); }); dt_basic.on('click', 'a.item-delete', function (e) { let tr = e.target.closest('tr'); let row = dt_basic.row(tr) let rowData = row.data(); let userId = rowData['id']; $('#removeForm input[name="ID"]').val(userId); }); dt_basic.on('click', 'button.item-activation', function (e) { let tr = e.target.closest('tr'); let row = dt_basic.row(tr) let rowData = row.data(); let userId = rowData['id']; let enabled = rowData['isEnabled']; console.log(`${userId}: ${enabled}`); api.users.activate(userId, {enabled: enabled}).then(data => { const toastMSG = new toastMessage("Activate User", data.body, data.title, data.background); const toastElement = toastMSG.getElement(); toastContainer.append(toastElement); bootstrap.Toast.getOrCreateInstance(toastElement).show(); }).catch(err => { console.log(err); }).finally(() => { dt_basic.ajax.reload(); }); }); } }); } catch(err) { console.error(err); alert(err); } } // Load interface options api.servers.getAll().then((servers) => { $.each(servers, function(index) { let server = servers[index]; $('#add-interface').append(''); $('#edit-interface').append(''); }); }); // Toggle dynamic inputs toggleDynamicInput($('#add-dns-dynamic'), $('#add-dns-server')); toggleDynamicInput($('#edit-dns-dynamic'), $('#edit-dns-address')); toggleDynamicInput($('#add-ip-dynamic'), $('#add-allowed-address')); toggleDynamicInput($('#edit-ip-dynamic'), $('#edit-allowed-address')); // FormValidation // Add user form field types $('#add-expire').flatpickr({ enableTime: true, altInput: true, altFormat: "Y-m-d H:i", dateFormat: "Z" }); // Add user form $('#addUserForm').on('submit', e => { let form = $(e.target); let data = new FormData(e.target); api.users.create({ name: data.get('Username'), password: data.get('Password'), interface: data.get('Interface'), privateKey: data.get('PrivateKey'), publicKey: data.get('PublicKey'), presharedKey: data.get('PresharedKey'), inheritIP: data.get('InheritIP') == 'on', allowedAddress: data.get('AllowedAddress'), endpointAddress: data.get('Endpoint'), endpointPort: data.get('EndpointPort') || null, keepalive: data.get('KeepAlive') || null, inheritDNS: data.get('InheritDNS') == 'on', dnsAddress: data.get('DNSAddress'), expire: data.get('Expire'), traffic: data.get('Traffic'), enabled: form.find('button.btn-toggle').attr('aria-pressed') == 'true' }).then(data => { const toastMSG = new toastMessage("Create User", data.body, data.title, data.background); const toastElement = toastMSG.getElement(); toastContainer.append(toastElement); bootstrap.Toast.getOrCreateInstance(toastElement).show(); }).catch(err => { console.error(err); }).finally(() => { dt_basic.ajax.reload(); }); }); // Edit user form $('#editUserForm').on('submit', e => { let form = $(e.target); let data = new FormData(e.target); api.users.update(data.get('ID'), { name: data.get('Username') || null, password: data.get('Password') || null, interface: data.get('Interface'), privateKey: data.get('PrivateKey') || null, publicKey: data.get('PublicKey') || null, presharedKey: data.get('PresharedKey') || null, inheritIP: data.get('InheritIP') == 'on', allowedAddress: data.get('AllowedAddress') || null, endpointAddress: data.get('Endpoint') || null, endpointPort: data.get('EndpointPort') || null, keepalive: data.get('KeepAlive') || null, inheritDNS: data.get('InheritDNS') == 'on', dnsAddress: data.get('DNSAddress') || null, expire: data.get('Expire') || null, traffic: data.get('Traffic') || null, enabled: form.find('button.btn-toggle').attr('aria-pressed') == 'true' }).then(data => { const toastMSG = new toastMessage("Update User", data.body, data.title, data.background); const toastElement = toastMSG.getElement(); toastContainer.append(toastElement); bootstrap.Toast.getOrCreateInstance(toastElement).show(); }).catch(err => { console.error(err); }).finally(() => { dt_basic.ajax.reload(); }); }); // Sync user form $('#syncUserForm').on('submit', e => { let data = new FormData(e.target); api.users.sync(data.get('ID'), { name: data.get('Username') || null, password: data.get('Password') || null, privateKey: data.get('PrivateKey') || null, publicKey: data.get('PublicKey') || null }).then(data => { const toastMSG = new toastMessage("Sync User", data.body, data.title, data.background); const toastElement = toastMSG.getElement(); toastContainer.append(toastElement); bootstrap.Toast.getOrCreateInstance(toastElement).show(); }).catch(err => { console.error(err); }).finally(() => { dt_basic.ajax.reload(); }); }); // Delete user form $('#removeForm').on('submit', e => { let data = new FormData(e.target); api.users.delete(data.get('ID')).then(data => { const toastMSG = new toastMessage("Delete User", data.body, data.title, data.background); const toastElement = toastMSG.getElement(); toastContainer.append(toastElement); bootstrap.Toast.getOrCreateInstance(toastElement).show(); }).catch(err => { console.error(err); return; }).finally(() => { dt_basic.ajax.reload(); new bootstrap.Modal('#RemoveModal').hide(); }); }); }); function toggleDynamicInput(checkbox, element) { checkbox.on('change', function() { element.prop('disabled', checkbox.is(':checked')); }); }