diff --git a/app/js/custom.js b/app/js/ajax/main.js
similarity index 53%
rename from app/js/custom.js
rename to app/js/ajax/main.js
index 090cc826..c9dae9ea 100644
--- a/app/js/custom.js
+++ b/app/js/ajax/main.js
@@ -1,21 +1,3 @@
-function msgShow(retcode,msg) {
- if(retcode == 0) { var alertType = 'success';
- } else if(retcode == 2 || retcode == 1) {
- var alertType = 'danger';
- }
- var htmlMsg = '
'+msg+'
';
- return htmlMsg;
-}
-
-function createNetmaskAddr(bitCount) {
- var mask=[];
- for(i=0;i<4;i++) {
- var n = Math.min(bitCount, 8);
- mask.push(256 - Math.pow(2, 8-n));
- bitCount -= n;
- }
- return mask.join('.');
-}
function loadSummary(strInterface) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
@@ -38,94 +20,6 @@ function getAllInterfaces() {
});
}
-function setupTabs() {
- $('a[data-bs-toggle="tab"]').on('shown.bs.tab',function(e){
- var target = $(e.target).attr('href');
- if(!target.match('summary')) {
- var int = target.replace("#","");
- // loadCurrentSettings(int);
- }
- });
-}
-
-$(document).on("click", ".js-add-dhcp-static-lease", function(e) {
- e.preventDefault();
- var container = $(".js-new-dhcp-static-lease");
- var mac = $("input[name=mac]", container).val().trim();
- var ip = $("input[name=ip]", container).val().trim();
- var comment = $("input[name=comment]", container).val().trim();
- if (mac == "" || ip == "") {
- return;
- }
- var row = $("#js-dhcp-static-lease-row").html()
- .replace("{{ mac }}", mac)
- .replace("{{ ip }}", ip)
- .replace("{{ comment }}", comment);
- $(".js-dhcp-static-lease-container").append(row);
-
- $("input[name=mac]", container).val("");
- $("input[name=ip]", container).val("");
- $("input[name=comment]", container).val("");
-});
-
-$(document).on("click", ".js-remove-dhcp-static-lease", function(e) {
- e.preventDefault();
- $(this).parents(".js-dhcp-static-lease-row").remove();
-});
-
-$(document).on("submit", ".js-dhcp-settings-form", function(e) {
- $(".js-add-dhcp-static-lease").trigger("click");
-});
-
-$(document).on("click", ".js-add-dhcp-upstream-server", function(e) {
- e.preventDefault();
-
- var field = $("#add-dhcp-upstream-server-field")
- var row = $("#dhcp-upstream-server").html().replace("{{ server }}", field.val())
-
- if (field.val().trim() == "") { return }
-
- $(".js-dhcp-upstream-servers").append(row)
-
- field.val("")
-});
-
-$(document).on("click", ".js-remove-dhcp-upstream-server", function(e) {
- e.preventDefault();
- $(this).parents(".js-dhcp-upstream-server").remove();
-});
-
-$(document).on("submit", ".js-dhcp-settings-form", function(e) {
- $(".js-add-dhcp-upstream-server").trigger("click");
-});
-
-/**
- * mark a form field, e.g. a select box, with the class `.js-field-preset`
- * and give it an attribute `data-field-preset-target` with a text field's
- * css selector.
- *
- * now, if the element marked `.js-field-preset` receives a `change` event,
- * its value will be copied to all elements matching the selector in
- * data-field-preset-target.
- */
-$(document).on("change", ".js-field-preset", function(e) {
- var selector = this.getAttribute("data-field-preset-target")
- var value = "" + this.value
- var syncValue = function(el) { el.value = value }
-
- if (value.trim() === "") { return }
-
- document.querySelectorAll(selector).forEach(syncValue)
-});
-
-$(document).on("click", "#gen_wpa_passphrase", function(e) {
- $('#txtwpapassphrase').val(genPassword(63));
-});
-
-$(document).on("click", "#gen_apikey", function(e) {
- $('#txtapikey').val(genPassword(32).toLowerCase());
-});
-
$(document).on("click", "#js-clearhostapd-log", function(e) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/hostapd.log', 'csrf_token': csrfToken},function(data){
@@ -150,54 +44,6 @@ $(document).on("click", "#js-clearopenvpn-log", function(e) {
});
});
-
-// Enable Bootstrap tooltips
-$(function () {
- $('[data-bs-toggle="tooltip"]').tooltip()
-})
-
-function genPassword(pwdLen) {
- var pwdChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- var rndPass = Array(pwdLen).fill(pwdChars).map(function(x) { return x[Math.floor(Math.random() * x.length)] }).join('');
- return rndPass;
-}
-
-function setupBtns() {
- $('#btnSummaryRefresh').click(function(){getAllInterfaces();});
- $('.intsave').click(function(){
- var int = $(this).data('int');
- saveNetworkSettings(int);
- });
- $('.intapply').click(function(){
- applyNetworkSettings();
- });
-}
-
-function setCSRFTokenHeader(event, xhr, settings) {
- var csrfToken = $('meta[name=csrf_token]').attr('content');
- if (/^(POST|PATCH|PUT|DELETE)$/i.test(settings.type)) {
- xhr.setRequestHeader("X-CSRF-Token", csrfToken);
- }
-}
-
-function contentLoaded() {
- pageCurrent = window.location.href.split("/").pop();
- switch(pageCurrent) {
- case "network_conf":
- getAllInterfaces();
- setupTabs();
- setupBtns();
- break;
- case "hostapd_conf":
- getChannel();
- setHardwareModeTooltip();
- break;
- case "dhcpd_conf":
- loadInterfaceDHCPSelect();
- break;
- }
-}
-
function loadWifiStations(refresh) {
return function() {
var complete = function() { $(this).removeClass('loading-spinner'); }
@@ -257,26 +103,6 @@ function loadInterfaceDHCPSelect() {
});
}
-function setDHCPToggles(state) {
- if ($('#chkfallback').is(':checked') && state) {
- $('#chkfallback').prop('checked', state);
- }
- if ($('#dhcp-iface').is(':checked') && !state) {
- $('#dhcp-iface').prop('checked', state);
- setDhcpFieldsDisabled();
- }
- $('#chkfallback').prop('disabled', state);
- $('#dhcp-iface').prop('disabled', !state);
-}
-
-$('#chkfallback').change(function() {
- if ($('#chkfallback').is(':checked')) {
- setStaticFieldsEnabled();
- } else {
- setStaticFieldsDisabled();
- }
-});
-
$('#debugModal').on('shown.bs.modal', function (e) {
var csrfToken = $('meta[name=csrf_token]').attr('content');
$.post('ajax/system/sys_debug.php',{'csrf_token': csrfToken},function(data){
@@ -327,10 +153,6 @@ $('#performUpdate').on('submit', function(event) {
$('#performupdateModal').modal('show');
});
-$('#performupdateModal').on('shown.bs.modal', function (e) {
- fetchUpdateResponse();
-});
-
function fetchUpdateResponse() {
const complete = 6;
const error = 7;
@@ -372,22 +194,6 @@ function fetchUpdateResponse() {
});
}
-$('#hostapdModal').on('shown.bs.modal', function (e) {
- var seconds = 3;
- var pct = 0;
- var countDown = setInterval(function(){
- if(seconds <= 0){
- clearInterval(countDown);
- }
- document.getElementsByClassName('progress-bar').item(0).setAttribute('style','width:'+Number(pct)+'%');
- seconds --;
- pct = Math.floor(100-(seconds*100/4));
- }, 500);
-});
-
-$('#configureClientModal').on('shown.bs.modal', function (e) {
-});
-
$('#ovpn-confirm-delete').on('click', '.btn-delete', function (e) {
var cfg_id = $(this).data('recordId');
var csrfToken = $('meta[name=csrf_token]').attr('content');
@@ -418,21 +224,6 @@ $('#ovpn-confirm-activate').on('click', '.btn-activate', function (e) {
});
});
-$('#ovpn-confirm-activate').on('shown.bs.modal', function (e) {
- var data = $(e.relatedTarget).data();
- $('.btn-activate', this).data('recordId', data.recordId);
-});
-
-$('#ovpn-userpw,#ovpn-certs').on('click', function (e) {
- if (this.id == 'ovpn-userpw') {
- $('#PanelCerts').hide();
- $('#PanelUserPW').show();
- } else if (this.id == 'ovpn-certs') {
- $('#PanelUserPW').hide();
- $('#PanelCerts').show();
- }
-});
-
$('#js-system-reset-confirm').on('click', function (e) {
var progressText = $('#js-system-reset-confirm').attr('data-message');
var successHtml = $('#system-reset-message').attr('data-message');
@@ -463,49 +254,6 @@ $('#js-sys-reboot, #js-sys-shutdown').on('click', function (e) {
});
});
-$('#install-user-plugin').on('shown.bs.modal', function (e) {
- var button = $(e.relatedTarget);
- $(this).data('button', button);
- var manifestData = button.data('plugin-manifest');
- var installed = button.data('plugin-installed') || false;
- var repoPublic = button.data('repo-public') || false;
- var installPath = manifestData.install_path;
-
- if (!installed && repoPublic && installPath === 'plugins-available') {
- insidersHTML = 'Available with Insiders';
- $('#plugin-additional').html(insidersHTML);
- } else {
- $('#plugin-additional').empty();
- }
- if (manifestData) {
- $('#plugin-docs').html(manifestData.plugin_docs
- ? `${manifestData.plugin_docs}`
- : 'Unknown');
- $('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`);
- $('#plugin-name').text(manifestData.name || 'Unknown');
- $('#plugin-version').text(manifestData.version || 'Unknown');
- $('#plugin-description').text(manifestData.description || 'No description provided');
- $('#plugin-author').html(manifestData.author
- ? manifestData.author + (manifestData.author_uri
- ? ` (profile)` : '') : 'Unknown');
- $('#plugin-license').text(manifestData.license || 'Unknown');
- $('#plugin-locale').text(manifestData.default_locale || 'Unknown');
- $('#plugin-configuration').html(formatProperty(manifestData.configuration || 'None'));
- $('#plugin-packages').html(formatProperty(manifestData.keys || 'None'));
- $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || 'None'));
- $('#plugin-javascript').html(formatProperty(manifestData.javascript || 'None'));
- $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || 'None'));
- $('#plugin-user-name').html((manifestData.user_nonprivileged && manifestData.user_nonprivileged.name) || 'None');
- }
- if (installed) {
- $('#js-install-plugin-confirm').html('OK');
- } else if (!installed && repoPublic && installPath == 'plugins-available') {
- $('#js-install-plugin-confirm').html('Get Insiders');
- } else {
- $('#js-install-plugin-confirm').html('Install now');
- }
-});
-
$('#js-install-plugin-confirm').on('click', function (e) {
var button = $('#install-user-plugin').data('button');
var manifestData = button.data('plugin-manifest');
@@ -572,75 +320,7 @@ $('#js-install-plugin-confirm').on('click', function (e) {
}
});
-$('#js-install-plugin-ok').on('click', function (e) {
- $("#install-plugin-progress").modal('hide');
- window.location.reload();
-});
-
-function formatProperty(prop) {
- if (Array.isArray(prop)) {
- if (typeof prop[0] === 'object') {
- return prop.map(item => {
- return Object.entries(item)
- .map(([key, value]) => `${key}: ${value}`)
- .join('
');
- }).join('
');
- }
- return prop.map(line => `${line}
`).join('');
- }
- if (typeof prop === 'object') {
- return Object.entries(prop)
- .map(([key, value]) => `${key}: ${value}`)
- .join('
');
- }
- return prop || 'None';
-}
-
-$(document).ready(function(){
- $("#PanelManual").hide();
- $('.ip_address').mask('0ZZ.0ZZ.0ZZ.0ZZ', {
- translation: {
- 'Z': {
- pattern: /[0-9]/, optional: true
- }
- },
- placeholder: "___.___.___.___"
- });
- $('.mac_address').mask('FF:FF:FF:FF:FF:FF', {
- translation: {
- 'F': {
- pattern: /[0-9a-fA-F]/, optional: false
- }
- },
- placeholder: "__:__:__:__:__:__"
- });
-});
-
-$(document).ready(function() {
- $('.cidr').mask('099.099.099.099/099', {
- translation: {
- '0': { pattern: /[0-9]/ }
- },
- placeholder: "___.___.___.___/___"
- });
-});
-
-$('#wg-upload,#wg-manual').on('click', function (e) {
- if (this.id == 'wg-upload') {
- $('#PanelManual').hide();
- $('#PanelUpload').show();
- } else if (this.id == 'wg-manual') {
- $('#PanelUpload').hide();
- $('#PanelManual').show();
- }
-});
-
-$(".custom-file-input").on("change", function() {
- var fileName = $(this).val().split("\\").pop();
- $(this).siblings(".custom-file-label").addClass("selected").html(fileName);
-});
-
- // Retrieves the 'channel' value specified in hostapd.conf
+// Retrieves the 'channel' value specified in hostapd.conf
function getChannel() {
$.get('ajax/networking/get_channel.php',function(data){
jsonData = JSON.parse(data);
@@ -827,22 +507,6 @@ $('.wg-client-dl').click(function(){
req.send();
})
-// Event listener for Bootstrap's form validation
-window.addEventListener('load', function() {
- // Fetch all the forms we want to apply custom Bootstrap validation styles to
- var forms = document.getElementsByClassName('needs-validation');
- // Loop over them and prevent submission
- var validation = Array.prototype.filter.call(forms, function(form) {
- form.addEventListener('submit', function(event) {
- if (form.checkValidity() === false) {
- event.preventDefault();
- event.stopPropagation();
- }
- form.classList.add('was-validated');
- }, false);
- });
-}, false);
-
let sessionCheckInterval = setInterval(checkSession, 5000);
function checkSession() {
@@ -861,249 +525,3 @@ function checkSession() {
});
}
-function showSessionExpiredModal() {
- $('#sessionTimeoutModal').modal('show');
-}
-
-$(document).on("click", "#js-session-expired-login", function(e) {
- const loginModal = $('#modal-admin-login');
- const redirectUrl = window.location.pathname;
- window.location.href = `/login?action=${encodeURIComponent(redirectUrl)}`;
-});
-
-// show modal login on page load
-$(document).ready(function () {
- const params = new URLSearchParams(window.location.search);
- const redirectUrl = $('#redirect-url').val() || params.get('action') || '/';
- $('#modal-admin-login').modal('show');
- $('#redirect-url').val(redirectUrl);
- $('#username').focus();
- $('#username').addClass("focusedInput");
-});
-
-// DHCP or Static IP option group
-$('#chkstatic').on('change', function() {
- if (this.checked) {
- setStaticFieldsEnabled();
- }
-});
-
-$('#chkdhcp').on('change', function() {
- this.checked ? setStaticFieldsDisabled() : null;
-});
-
-
-$('input[name="dhcp-iface"]').change(function() {
- if ($('input[name="dhcp-iface"]:checked').val() == '1') {
- setDhcpFieldsEnabled();
- } else {
- setDhcpFieldsDisabled();
- }
-});
-
-
-function setStaticFieldsEnabled() {
- $('#txtipaddress').prop('required', true);
- $('#txtsubnetmask').prop('required', true);
- $('#txtgateway').prop('required', true);
-
- $('#txtipaddress').removeAttr('disabled');
- $('#txtsubnetmask').removeAttr('disabled');
- $('#txtgateway').removeAttr('disabled');
-}
-
-function setStaticFieldsDisabled() {
- $('#txtipaddress').prop('disabled', true);
- $('#txtsubnetmask').prop('disabled', true);
- $('#txtgateway').prop('disabled', true);
-
- $('#txtipaddress').removeAttr('required');
- $('#txtsubnetmask').removeAttr('required');
- $('#txtgateway').removeAttr('required');
-}
-
-function setDhcpFieldsEnabled() {
- $('#txtrangestart').prop('required', true);
- $('#txtrangeend').prop('required', true);
- $('#txtrangeleasetime').prop('required', true);
- $('#cbxrangeleasetimeunits').prop('required', true);
-
- $('#txtrangestart').removeAttr('disabled');
- $('#txtrangeend').removeAttr('disabled');
- $('#txtrangeleasetime').removeAttr('disabled');
- $('#cbxrangeleasetimeunits').removeAttr('disabled');
- $('#txtdns1').removeAttr('disabled');
- $('#txtdns2').removeAttr('disabled');
- $('#txtmetric').removeAttr('disabled');
-}
-
-function setDhcpFieldsDisabled() {
- $('#txtrangestart').removeAttr('required');
- $('#txtrangeend').removeAttr('required');
- $('#txtrangeleasetime').removeAttr('required');
- $('#cbxrangeleasetimeunits').removeAttr('required');
-
- $('#txtrangestart').prop('disabled', true);
- $('#txtrangeend').prop('disabled', true);
- $('#txtrangeleasetime').prop('disabled', true);
- $('#cbxrangeleasetimeunits').prop('disabled', true);
- $('#txtdns1').prop('disabled', true);
- $('#txtdns2').prop('disabled', true);
- $('#txtmetric').prop('disabled', true);
-}
-
-// Static Array method
-Array.range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);
-
-$(document).on("click", ".js-toggle-password", function(e) {
- var button = $(e.currentTarget);
- var field = $(button.data("bsTarget"));
- if (field.is(":input")) {
- e.preventDefault();
-
- if (!button.data("__toggle-with-initial")) {
- $("i", button).removeClass("fas fa-eye").addClass(button.attr("data-toggle-with"));
- }
-
- if (field.attr("type") === "password") {
- field.attr("type", "text");
- } else {
- $("i", button).removeClass("fas fa-eye-slash").addClass("fas fa-eye");
- field.attr("type", "password");
- }
- }
-});
-
-$(function() {
- $('#theme-select').change(function() {
- var theme = themes[$( "#theme-select" ).val() ];
-
- var hasDarkTheme = theme === 'custom.php';
- var nightModeChecked = $("#night-mode").prop("checked");
-
- if (nightModeChecked && hasDarkTheme) {
- if (theme === "custom.php") {
- set_theme("dark.css");
- }
- } else {
- set_theme(theme);
- }
- });
-});
-
-function set_theme(theme) {
- $('link[title="main"]').attr('href', 'app/css/' + theme);
- // persist selected theme in cookie
- setCookie('theme',theme,90);
-}
-
-$(function() {
- var currentTheme = getCookie('theme');
- // Check if the current theme is a dark theme
- var isDarkTheme = currentTheme === 'dark.css';
-
- $('#night-mode').prop('checked', isDarkTheme);
- $('#night-mode').change(function() {
- var state = $(this).is(':checked');
- var currentTheme = getCookie('theme');
-
- if (state == true) {
- if (currentTheme == 'custom.php') {
- set_theme('dark.css');
- }
- } else {
- if (currentTheme == 'dark.css') {
- set_theme('custom.php');
- }
- }
- });
-});
-
-function setCookie(cname, cvalue, exdays) {
- var d = new Date();
- d.setTime(d.getTime() + (exdays*24*60*60*1000));
- var expires = "expires="+ d.toUTCString();
- document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
-}
-
-function getCookie(cname) {
- var regx = new RegExp(cname + "=([^;]+)");
- var value = regx.exec(document.cookie);
- return (value != null) ? unescape(value[1]) : null;
-}
-
-// Define themes
-var themes = {
- "default": "custom.php",
- "hackernews" : "hackernews.css"
-}
-
-// Adds active class to current nav-item
-$(window).bind("load", function() {
- var url = window.location;
- $('.sb-nav-link-icon a').filter(function() {
- return this.href == url;
- }).parent().addClass('active');
-});
-
-// Sets focus on a specified tab
-document.addEventListener("DOMContentLoaded", function () {
- const params = new URLSearchParams(window.location.search);
- const targetTab = params.get("tab");
- if (targetTab) {
- let tabElement = document.querySelector(`[data-bs-toggle="tab"][href="#${targetTab}"]`);
- if (tabElement) {
- let tab = new bootstrap.Tab(tabElement);
- tab.show();
- }
- }
-});
-
-function disableValidation(form) {
- form.removeAttribute("novalidate");
- form.classList.remove("needs-validation");
- form.querySelectorAll("[required]").forEach(function (field) {
- field.removeAttribute("required");
- });
-}
-
-function updateActivityLED() {
- const threshold_bytes = 300;
- fetch('/app/net_activity')
- .then(res => res.text())
- .then(data => {
- const activity = parseInt(data.trim());
- const leds = document.querySelectorAll('.hostapd-led');
-
- if (!isNaN(activity)) {
- leds.forEach(led => {
- if (activity > threshold_bytes) {
- led.classList.add('led-pulse');
- setTimeout(() => {
- led.classList.remove('led-pulse');
- }, 50);
- } else {
- led.classList.remove('led-pulse');
- }
- });
- }
- })
- .catch(() => { /* ignore fetch errors */ });
-}
-setInterval(updateActivityLED, 100);
-
-$(document).ready(function() {
- const $htmlElement = $('html');
- const $modeswitch = $('#night-mode');
- $modeswitch.on('change', function() {
- const isChecked = $(this).is(':checked');
- const newTheme = isChecked ? 'dark' : 'light';
- $htmlElement.attr('data-bs-theme', newTheme);
- localStorage.setItem('bsTheme', newTheme);
- });
-});
-
-$(document)
- .ajaxSend(setCSRFTokenHeader)
- .ready(contentLoaded)
- .ready(loadWifiStations());
diff --git a/app/js/ui/main.js b/app/js/ui/main.js
new file mode 100644
index 00000000..97824fcc
--- /dev/null
+++ b/app/js/ui/main.js
@@ -0,0 +1,583 @@
+
+function msgShow(retcode,msg) {
+ if(retcode == 0) { var alertType = 'success';
+ } else if(retcode == 2 || retcode == 1) {
+ var alertType = 'danger';
+ }
+ var htmlMsg = ''+msg+'
';
+ return htmlMsg;
+}
+
+function createNetmaskAddr(bitCount) {
+ var mask=[];
+ for(i=0;i<4;i++) {
+ var n = Math.min(bitCount, 8);
+ mask.push(256 - Math.pow(2, 8-n));
+ bitCount -= n;
+ }
+ return mask.join('.');
+}
+
+function setupTabs() {
+ $('a[data-bs-toggle="tab"]').on('shown.bs.tab',function(e){
+ var target = $(e.target).attr('href');
+ if(!target.match('summary')) {
+ var int = target.replace("#","");
+ }
+ });
+}
+
+$(document).on("click", ".js-add-dhcp-static-lease", function(e) {
+ e.preventDefault();
+ var container = $(".js-new-dhcp-static-lease");
+ var mac = $("input[name=mac]", container).val().trim();
+ var ip = $("input[name=ip]", container).val().trim();
+ var comment = $("input[name=comment]", container).val().trim();
+ if (mac == "" || ip == "") {
+ return;
+ }
+ var row = $("#js-dhcp-static-lease-row").html()
+ .replace("{{ mac }}", mac)
+ .replace("{{ ip }}", ip)
+ .replace("{{ comment }}", comment);
+ $(".js-dhcp-static-lease-container").append(row);
+
+ $("input[name=mac]", container).val("");
+ $("input[name=ip]", container).val("");
+ $("input[name=comment]", container).val("");
+});
+
+$(document).on("click", ".js-remove-dhcp-static-lease", function(e) {
+ e.preventDefault();
+ $(this).parents(".js-dhcp-static-lease-row").remove();
+});
+
+$(document).on("submit", ".js-dhcp-settings-form", function(e) {
+ $(".js-add-dhcp-static-lease").trigger("click");
+});
+
+$(document).on("click", ".js-add-dhcp-upstream-server", function(e) {
+ e.preventDefault();
+
+ var field = $("#add-dhcp-upstream-server-field")
+ var row = $("#dhcp-upstream-server").html().replace("{{ server }}", field.val())
+
+ if (field.val().trim() == "") { return }
+
+ $(".js-dhcp-upstream-servers").append(row)
+
+ field.val("")
+});
+
+$(document).on("click", ".js-remove-dhcp-upstream-server", function(e) {
+ e.preventDefault();
+ $(this).parents(".js-dhcp-upstream-server").remove();
+});
+
+$(document).on("submit", ".js-dhcp-settings-form", function(e) {
+ $(".js-add-dhcp-upstream-server").trigger("click");
+});
+
+/**
+ * mark a form field, e.g. a select box, with the class `.js-field-preset`
+ * and give it an attribute `data-field-preset-target` with a text field's
+ * css selector.
+ *
+ * now, if the element marked `.js-field-preset` receives a `change` event,
+ * its value will be copied to all elements matching the selector in
+ * data-field-preset-target.
+ */
+$(document).on("change", ".js-field-preset", function(e) {
+ var selector = this.getAttribute("data-field-preset-target")
+ var value = "" + this.value
+ var syncValue = function(el) { el.value = value }
+
+ if (value.trim() === "") { return }
+
+ document.querySelectorAll(selector).forEach(syncValue)
+});
+
+$(document).on("click", "#gen_wpa_passphrase", function(e) {
+ $('#txtwpapassphrase').val(genPassword(63));
+});
+
+$(document).on("click", "#gen_apikey", function(e) {
+ $('#txtapikey').val(genPassword(32).toLowerCase());
+});
+
+// Enable Bootstrap tooltips
+$(function () {
+ $('[data-bs-toggle="tooltip"]').tooltip()
+})
+
+function genPassword(pwdLen) {
+ var pwdChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ var rndPass = Array(pwdLen).fill(pwdChars).map(function(x) { return x[Math.floor(Math.random() * x.length)] }).join('');
+ return rndPass;
+}
+
+function setupBtns() {
+ $('#btnSummaryRefresh').click(function(){getAllInterfaces();});
+ $('.intsave').click(function(){
+ var int = $(this).data('int');
+ saveNetworkSettings(int);
+ });
+ $('.intapply').click(function(){
+ applyNetworkSettings();
+ });
+}
+
+function setCSRFTokenHeader(event, xhr, settings) {
+ var csrfToken = $('meta[name=csrf_token]').attr('content');
+ if (/^(POST|PATCH|PUT|DELETE)$/i.test(settings.type)) {
+ xhr.setRequestHeader("X-CSRF-Token", csrfToken);
+ }
+}
+
+function contentLoaded() {
+ pageCurrent = window.location.href.split("/").pop();
+ switch(pageCurrent) {
+ case "network_conf":
+ getAllInterfaces();
+ setupTabs();
+ setupBtns();
+ break;
+ case "hostapd_conf":
+ getChannel();
+ setHardwareModeTooltip();
+ break;
+ case "dhcpd_conf":
+ loadInterfaceDHCPSelect();
+ break;
+ }
+}
+
+function setDHCPToggles(state) {
+ if ($('#chkfallback').is(':checked') && state) {
+ $('#chkfallback').prop('checked', state);
+ }
+ if ($('#dhcp-iface').is(':checked') && !state) {
+ $('#dhcp-iface').prop('checked', state);
+ setDhcpFieldsDisabled();
+ }
+ $('#chkfallback').prop('disabled', state);
+ $('#dhcp-iface').prop('disabled', !state);
+}
+
+$('#chkfallback').change(function() {
+ if ($('#chkfallback').is(':checked')) {
+ setStaticFieldsEnabled();
+ } else {
+ setStaticFieldsDisabled();
+ }
+});
+
+$('#performupdateModal').on('shown.bs.modal', function (e) {
+ fetchUpdateResponse();
+});
+
+$('#hostapdModal').on('shown.bs.modal', function (e) {
+ var seconds = 3;
+ var pct = 0;
+ var countDown = setInterval(function(){
+ if(seconds <= 0){
+ clearInterval(countDown);
+ }
+ document.getElementsByClassName('progress-bar').item(0).setAttribute('style','width:'+Number(pct)+'%');
+ seconds --;
+ pct = Math.floor(100-(seconds*100/4));
+ }, 500);
+});
+
+$('#configureClientModal').on('shown.bs.modal', function (e) {
+});
+
+$('#ovpn-confirm-activate').on('shown.bs.modal', function (e) {
+ var data = $(e.relatedTarget).data();
+ $('.btn-activate', this).data('recordId', data.recordId);
+});
+
+$('#ovpn-userpw,#ovpn-certs').on('click', function (e) {
+ if (this.id == 'ovpn-userpw') {
+ $('#PanelCerts').hide();
+ $('#PanelUserPW').show();
+ } else if (this.id == 'ovpn-certs') {
+ $('#PanelUserPW').hide();
+ $('#PanelCerts').show();
+ }
+});
+
+$('#install-user-plugin').on('shown.bs.modal', function (e) {
+ var button = $(e.relatedTarget);
+ $(this).data('button', button);
+ var manifestData = button.data('plugin-manifest');
+ var installed = button.data('plugin-installed') || false;
+ var repoPublic = button.data('repo-public') || false;
+ var installPath = manifestData.install_path;
+
+ if (!installed && repoPublic && installPath === 'plugins-available') {
+ insidersHTML = 'Available with Insiders';
+ $('#plugin-additional').html(insidersHTML);
+ } else {
+ $('#plugin-additional').empty();
+ }
+ if (manifestData) {
+ $('#plugin-docs').html(manifestData.plugin_docs
+ ? `${manifestData.plugin_docs}`
+ : 'Unknown');
+ $('#plugin-icon').attr('class', `${manifestData.icon || 'fas fa-plug'} link-secondary h5 me-2`);
+ $('#plugin-name').text(manifestData.name || 'Unknown');
+ $('#plugin-version').text(manifestData.version || 'Unknown');
+ $('#plugin-description').text(manifestData.description || 'No description provided');
+ $('#plugin-author').html(manifestData.author
+ ? manifestData.author + (manifestData.author_uri
+ ? ` (profile)` : '') : 'Unknown');
+ $('#plugin-license').text(manifestData.license || 'Unknown');
+ $('#plugin-locale').text(manifestData.default_locale || 'Unknown');
+ $('#plugin-configuration').html(formatProperty(manifestData.configuration || 'None'));
+ $('#plugin-packages').html(formatProperty(manifestData.keys || 'None'));
+ $('#plugin-dependencies').html(formatProperty(manifestData.dependencies || 'None'));
+ $('#plugin-javascript').html(formatProperty(manifestData.javascript || 'None'));
+ $('#plugin-sudoers').html(formatProperty(manifestData.sudoers || 'None'));
+ $('#plugin-user-name').html((manifestData.user_nonprivileged && manifestData.user_nonprivileged.name) || 'None');
+ }
+ if (installed) {
+ $('#js-install-plugin-confirm').html('OK');
+ } else if (!installed && repoPublic && installPath == 'plugins-available') {
+ $('#js-install-plugin-confirm').html('Get Insiders');
+ } else {
+ $('#js-install-plugin-confirm').html('Install now');
+ }
+});
+
+$('#js-install-plugin-ok').on('click', function (e) {
+ $("#install-plugin-progress").modal('hide');
+ window.location.reload();
+});
+
+function formatProperty(prop) {
+ if (Array.isArray(prop)) {
+ if (typeof prop[0] === 'object') {
+ return prop.map(item => {
+ return Object.entries(item)
+ .map(([key, value]) => `${key}: ${value}`)
+ .join('
');
+ }).join('
');
+ }
+ return prop.map(line => `${line}
`).join('');
+ }
+ if (typeof prop === 'object') {
+ return Object.entries(prop)
+ .map(([key, value]) => `${key}: ${value}`)
+ .join('
');
+ }
+ return prop || 'None';
+}
+
+$(document).ready(function(){
+ $("#PanelManual").hide();
+ $('.ip_address').mask('0ZZ.0ZZ.0ZZ.0ZZ', {
+ translation: {
+ 'Z': {
+ pattern: /[0-9]/, optional: true
+ }
+ },
+ placeholder: "___.___.___.___"
+ });
+ $('.mac_address').mask('FF:FF:FF:FF:FF:FF', {
+ translation: {
+ 'F': {
+ pattern: /[0-9a-fA-F]/, optional: false
+ }
+ },
+ placeholder: "__:__:__:__:__:__"
+ });
+});
+
+$(document).ready(function() {
+ $('.cidr').mask('099.099.099.099/099', {
+ translation: {
+ '0': { pattern: /[0-9]/ }
+ },
+ placeholder: "___.___.___.___/___"
+ });
+});
+
+$('#wg-upload,#wg-manual').on('click', function (e) {
+ if (this.id == 'wg-upload') {
+ $('#PanelManual').hide();
+ $('#PanelUpload').show();
+ } else if (this.id == 'wg-manual') {
+ $('#PanelUpload').hide();
+ $('#PanelManual').show();
+ }
+});
+
+$(".custom-file-input").on("change", function() {
+ var fileName = $(this).val().split("\\").pop();
+ $(this).siblings(".custom-file-label").addClass("selected").html(fileName);
+});
+
+// Event listener for Bootstrap's form validation
+window.addEventListener('load', function() {
+ // Fetch all the forms we want to apply custom Bootstrap validation styles to
+ var forms = document.getElementsByClassName('needs-validation');
+ // Loop over them and prevent submission
+ var validation = Array.prototype.filter.call(forms, function(form) {
+ form.addEventListener('submit', function(event) {
+ if (form.checkValidity() === false) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ form.classList.add('was-validated');
+ }, false);
+ });
+}, false);
+
+function showSessionExpiredModal() {
+ $('#sessionTimeoutModal').modal('show');
+}
+
+$(document).on("click", "#js-session-expired-login", function(e) {
+ const loginModal = $('#modal-admin-login');
+ const redirectUrl = window.location.pathname;
+ window.location.href = `/login?action=${encodeURIComponent(redirectUrl)}`;
+});
+
+// show modal login on page load
+$(document).ready(function () {
+ const params = new URLSearchParams(window.location.search);
+ const redirectUrl = $('#redirect-url').val() || params.get('action') || '/';
+ $('#modal-admin-login').modal('show');
+ $('#redirect-url').val(redirectUrl);
+ $('#username').focus();
+ $('#username').addClass("focusedInput");
+});
+
+// DHCP or Static IP option group
+$('#chkstatic').on('change', function() {
+ if (this.checked) {
+ setStaticFieldsEnabled();
+ }
+});
+
+$('#chkdhcp').on('change', function() {
+ this.checked ? setStaticFieldsDisabled() : null;
+});
+
+
+$('input[name="dhcp-iface"]').change(function() {
+ if ($('input[name="dhcp-iface"]:checked').val() == '1') {
+ setDhcpFieldsEnabled();
+ } else {
+ setDhcpFieldsDisabled();
+ }
+});
+
+
+function setStaticFieldsEnabled() {
+ $('#txtipaddress').prop('required', true);
+ $('#txtsubnetmask').prop('required', true);
+ $('#txtgateway').prop('required', true);
+
+ $('#txtipaddress').removeAttr('disabled');
+ $('#txtsubnetmask').removeAttr('disabled');
+ $('#txtgateway').removeAttr('disabled');
+}
+
+function setStaticFieldsDisabled() {
+ $('#txtipaddress').prop('disabled', true);
+ $('#txtsubnetmask').prop('disabled', true);
+ $('#txtgateway').prop('disabled', true);
+
+ $('#txtipaddress').removeAttr('required');
+ $('#txtsubnetmask').removeAttr('required');
+ $('#txtgateway').removeAttr('required');
+}
+
+function setDhcpFieldsEnabled() {
+ $('#txtrangestart').prop('required', true);
+ $('#txtrangeend').prop('required', true);
+ $('#txtrangeleasetime').prop('required', true);
+ $('#cbxrangeleasetimeunits').prop('required', true);
+
+ $('#txtrangestart').removeAttr('disabled');
+ $('#txtrangeend').removeAttr('disabled');
+ $('#txtrangeleasetime').removeAttr('disabled');
+ $('#cbxrangeleasetimeunits').removeAttr('disabled');
+ $('#txtdns1').removeAttr('disabled');
+ $('#txtdns2').removeAttr('disabled');
+ $('#txtmetric').removeAttr('disabled');
+}
+
+function setDhcpFieldsDisabled() {
+ $('#txtrangestart').removeAttr('required');
+ $('#txtrangeend').removeAttr('required');
+ $('#txtrangeleasetime').removeAttr('required');
+ $('#cbxrangeleasetimeunits').removeAttr('required');
+
+ $('#txtrangestart').prop('disabled', true);
+ $('#txtrangeend').prop('disabled', true);
+ $('#txtrangeleasetime').prop('disabled', true);
+ $('#cbxrangeleasetimeunits').prop('disabled', true);
+ $('#txtdns1').prop('disabled', true);
+ $('#txtdns2').prop('disabled', true);
+ $('#txtmetric').prop('disabled', true);
+}
+
+// Static Array method
+Array.range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);
+
+$(document).on("click", ".js-toggle-password", function(e) {
+ var button = $(e.currentTarget);
+ var field = $(button.data("bsTarget"));
+ if (field.is(":input")) {
+ e.preventDefault();
+
+ if (!button.data("__toggle-with-initial")) {
+ $("i", button).removeClass("fas fa-eye").addClass(button.attr("data-toggle-with"));
+ }
+
+ if (field.attr("type") === "password") {
+ field.attr("type", "text");
+ } else {
+ $("i", button).removeClass("fas fa-eye-slash").addClass("fas fa-eye");
+ field.attr("type", "password");
+ }
+ }
+});
+
+$(function() {
+ $('#theme-select').change(function() {
+ var theme = themes[$( "#theme-select" ).val() ];
+
+ var hasDarkTheme = theme === 'custom.php';
+ var nightModeChecked = $("#night-mode").prop("checked");
+
+ if (nightModeChecked && hasDarkTheme) {
+ if (theme === "custom.php") {
+ set_theme("dark.css");
+ }
+ } else {
+ set_theme(theme);
+ }
+ });
+});
+
+function set_theme(theme) {
+ $('link[title="main"]').attr('href', 'app/css/' + theme);
+ // persist selected theme in cookie
+ setCookie('theme',theme,90);
+}
+
+$(function() {
+ var currentTheme = getCookie('theme');
+ // Check if the current theme is a dark theme
+ var isDarkTheme = currentTheme === 'dark.css';
+
+ $('#night-mode').prop('checked', isDarkTheme);
+ $('#night-mode').change(function() {
+ var state = $(this).is(':checked');
+ var currentTheme = getCookie('theme');
+
+ if (state == true) {
+ if (currentTheme == 'custom.php') {
+ set_theme('dark.css');
+ }
+ } else {
+ if (currentTheme == 'dark.css') {
+ set_theme('custom.php');
+ }
+ }
+ });
+});
+
+function setCookie(cname, cvalue, exdays) {
+ var d = new Date();
+ d.setTime(d.getTime() + (exdays*24*60*60*1000));
+ var expires = "expires="+ d.toUTCString();
+ document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
+}
+
+function getCookie(cname) {
+ var regx = new RegExp(cname + "=([^;]+)");
+ var value = regx.exec(document.cookie);
+ return (value != null) ? unescape(value[1]) : null;
+}
+
+// Define themes
+var themes = {
+ "default": "custom.php",
+ "hackernews" : "hackernews.css"
+}
+
+// Adds active class to current nav-item
+$(window).bind("load", function() {
+ var url = window.location;
+ $('.sb-nav-link-icon a').filter(function() {
+ return this.href == url;
+ }).parent().addClass('active');
+});
+
+// Sets focus on a specified tab
+document.addEventListener("DOMContentLoaded", function () {
+ const params = new URLSearchParams(window.location.search);
+ const targetTab = params.get("tab");
+ if (targetTab) {
+ let tabElement = document.querySelector(`[data-bs-toggle="tab"][href="#${targetTab}"]`);
+ if (tabElement) {
+ let tab = new bootstrap.Tab(tabElement);
+ tab.show();
+ }
+ }
+});
+
+function disableValidation(form) {
+ form.removeAttribute("novalidate");
+ form.classList.remove("needs-validation");
+ form.querySelectorAll("[required]").forEach(function (field) {
+ field.removeAttribute("required");
+ });
+}
+
+function updateActivityLED() {
+ const threshold_bytes = 300;
+ fetch('/app/net_activity')
+ .then(res => res.text())
+ .then(data => {
+ const activity = parseInt(data.trim());
+ const leds = document.querySelectorAll('.hostapd-led');
+
+ if (!isNaN(activity)) {
+ leds.forEach(led => {
+ if (activity > threshold_bytes) {
+ led.classList.add('led-pulse');
+ setTimeout(() => {
+ led.classList.remove('led-pulse');
+ }, 50);
+ } else {
+ led.classList.remove('led-pulse');
+ }
+ });
+ }
+ })
+ .catch(() => { /* ignore fetch errors */ });
+}
+setInterval(updateActivityLED, 100);
+
+$(document).ready(function() {
+ const $htmlElement = $('html');
+ const $modeswitch = $('#night-mode');
+ $modeswitch.on('change', function() {
+ const isChecked = $(this).is(':checked');
+ const newTheme = isChecked ? 'dark' : 'light';
+ $htmlElement.attr('data-bs-theme', newTheme);
+ localStorage.setItem('bsTheme', newTheme);
+ });
+});
+
+$(document)
+ .ajaxSend(setCSRFTokenHeader)
+ .ready(contentLoaded)
+ .ready(loadWifiStations());
+