diff --git a/scripts/pi-hole/js/login.js b/scripts/pi-hole/js/login.js
index fdd3f0d1..e30765f0 100644
--- a/scripts/pi-hole/js/login.js
+++ b/scripts/pi-hole/js/login.js
@@ -5,7 +5,7 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global utils:false */
+/* global utils:false, NProgress:false */
function getParams() {
var GETDict = {};
@@ -104,6 +104,7 @@ function wrongPassword(isError = false, isSuccess = false, data = null) {
function doLogin(password) {
wrongPassword(false, false, null);
+ NProgress.start();
utils.disableAll();
$.ajax({
url: "/api/auth",
@@ -115,10 +116,12 @@ function doLogin(password) {
})
.done(function (data) {
wrongPassword(false, true, data);
+ NProgress.done();
redirect();
})
.fail(function (data) {
wrongPassword(true, false, data);
+ NProgress.done();
utils.enableAll();
});
}
diff --git a/scripts/pi-hole/js/settings.js b/scripts/pi-hole/js/settings.js
index b8962396..a7998596 100644
--- a/scripts/pi-hole/js/settings.js
+++ b/scripts/pi-hole/js/settings.js
@@ -177,8 +177,8 @@ function saveSettings() {
"Successfully saved and applied settings",
""
);
- // Reload page
- location.reload();
+ // Show loading overlay
+ utils.loadingOverlay(true);
})
.fail(function (data, exception) {
utils.enableAll();
diff --git a/scripts/pi-hole/js/utils.js b/scripts/pi-hole/js/utils.js
index e2c0ed38..74bf07b6 100644
--- a/scripts/pi-hole/js/utils.js
+++ b/scripts/pi-hole/js/utils.js
@@ -5,7 +5,7 @@
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
-/* global moment:false, apiFailure: false, updateFtlInfo: false */
+/* global moment:false, apiFailure: false, updateFtlInfo: false, NProgress:false */
$(function () {
// CSRF protection for AJAX requests, this has to be configured globally
@@ -630,6 +630,46 @@ function listAlert(type, items, data) {
);
}
+// Callback function for the loading overlay timeout
+function loadingOverlayTimeoutCallback(reloadAfterTimeout) {
+ // Try to ping FTL to see if it finished restarting
+ $.ajax({
+ url: "/api/info/login",
+ method: "GET",
+ cache: false,
+ dataType: "json",
+ })
+ .done(function () {
+ // FTL is running again, hide loading overlay
+ NProgress.done();
+ if (reloadAfterTimeout) {
+ location.reload();
+ } else {
+ $(".wrapper").waitMe("hide");
+ }
+ })
+ .fail(function () {
+ // FTL is not running yet, try again in 500ms
+ setTimeout(loadingOverlayTimeoutCallback, 500, reloadAfterTimeout);
+ });
+}
+
+function loadingOverlay(reloadAfterTimeout = false) {
+ NProgress.start();
+ $(".wrapper").waitMe({
+ effect: "bounce",
+ text: "Pi-hole is currently applying your changes...",
+ bg: "rgba(0,0,0,0.7)",
+ color: "#fff",
+ maxSize: "",
+ textPos: "vertical",
+ });
+ // Start checking for FTL status after 2 seconds
+ setTimeout(loadingOverlayTimeoutCallback, 2000, reloadAfterTimeout);
+
+ return true;
+}
+
// Function that calls a function only if the page is currently visible. This is
// useful to prevent unnecessary API calls when the page is not visible (e.g.
// when the user is on another tab).
@@ -700,6 +740,7 @@ window.utils = (function () {
hexEncode: hexEncode,
hexDecode: hexDecode,
listsAlert: listAlert,
+ loadingOverlay: loadingOverlay,
setTimer: setTimer,
setInter: setInter,
};
diff --git a/scripts/pi-hole/lua/header.lp b/scripts/pi-hole/lua/header.lp
index 8e41a0e8..c4c81585 100644
--- a/scripts/pi-hole/lua/header.lp
+++ b/scripts/pi-hole/lua/header.lp
@@ -78,6 +78,8 @@ is_authenticated = mg.request_info.is_authenticated
html { background-color: #000; }
end ?>
+
+
if theme.name == 'lcars' then ?>
@@ -118,3 +120,5 @@ if startsWith(scriptname, 'groups') then
+
+
diff --git a/scripts/vendor/nprogress.min.js b/scripts/vendor/nprogress.min.js
new file mode 100644
index 00000000..34220a37
--- /dev/null
+++ b/scripts/vendor/nprogress.min.js
@@ -0,0 +1,128 @@
+/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
+ * @license MIT */
+(function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n():e.NProgress=n()})(this,function(){
+/**
+ * Helpers
+ */
+function e(e){return"object"==typeof HTMLElement?e instanceof HTMLElement:e&&"object"==typeof e&&1===e.nodeType&&"string"==typeof e.nodeName}function n(e,n,t){return et?t:e}
+/**
+ * (Internal) converts a percentage (`0..1`) to a bar translateX
+ * percentage (`-100%..0%`).
+ */function t(e){return 100*(-1+e)}
+/**
+ * (Internal) returns the correct CSS for changing the bar's
+ * position given an n percentage, and speed and ease from Settings
+ */function r(e,n,r){var i;return i="translate3d"===d.positionUsing?{transform:"translate3d("+t(e)+"%,0,0)"}:"translate"===d.positionUsing?{transform:"translate("+t(e)+"%,0)"}:{"margin-left":t(e)+"%"},i.transition="all "+n+"ms "+r,i}
+/**
+ * (Internal) Queues a function to be executed.
+ */
+/**
+ * (Internal) Determines if an element or space separated list of class names contains a class name.
+ */
+function i(e,n){var t="string"==typeof e?e:a(e);return t.indexOf(" "+n+" ")>=0}
+/**
+ * (Internal) Adds a class to an element.
+ */function o(e,n){var t=a(e),r=t+n;i(t,n)||(
+// Trim the opening space.
+e.className=r.substring(1))}
+/**
+ * (Internal) Removes a class from an element.
+ */function s(e,n){var t,r=a(e);i(e,n)&&(
+// Replace the class name.
+t=r.replace(" "+n+" "," "),
+// Trim the opening and closing spaces.
+e.className=t.substring(1,t.length-1))}
+/**
+ * (Internal) Gets a space separated list of the class names on the element.
+ * The list is wrapped with a single space on each end to facilitate finding
+ * matches within the list.
+ */function a(e){return(" "+(e&&e.className||"")+" ").replace(/\s+/gi," ")}
+/**
+ * (Internal) Removes an element from the DOM.
+ */function u(e){e&&e.parentNode&&e.parentNode.removeChild(e)}var c,l,f={version:"0.2.0"},d=f.settings={minimum:.08,easing:"linear",positionUsing:"",speed:200,trickle:!0,trickleSpeed:200,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};
+/**
+ * Updates configuration.
+ *
+ * NProgress.configure({
+ * minimum: 0.1
+ * });
+ */
+f.configure=function(e){var n,t;for(n in e)t=e[n],void 0!==t&&e.hasOwnProperty(n)&&(d[n]=t);return this},
+/**
+ * Last number.
+ */
+f.status=null,
+/**
+ * Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
+ *
+ * NProgress.set(0.4);
+ * NProgress.set(1.0);
+ */
+f.set=function(e){var t=f.isStarted();e=n(e,d.minimum,1),f.status=1===e?null:e;var i=f.render(!t),o=i.querySelector(d.barSelector),s=d.speed,a=d.easing;return i.offsetWidth,/* Repaint */
+p(function(n){
+// Set positionUsing if it hasn't already been set
+""===d.positionUsing&&(d.positionUsing=f.getPositioningCSS()),
+// Add transition
+m(o,r(e,s,a)),1===e?(
+// Fade out
+m(i,{transition:"none",opacity:1}),i.offsetWidth,/* Repaint */
+setTimeout(function(){m(i,{transition:"all "+s+"ms linear",opacity:0}),setTimeout(function(){f.remove(),n()},s)},s)):setTimeout(n,s)}),this},f.isStarted=function(){return"number"==typeof f.status},
+/**
+ * Shows the progress bar.
+ * This is the same as setting the status to 0%, except that it doesn't go backwards.
+ *
+ * NProgress.start();
+ *
+ */
+f.start=function(){f.status||f.set(0);var e=function(){setTimeout(function(){f.status&&(f.trickle(),e())},d.trickleSpeed)};return d.trickle&&e(),this},
+/**
+ * Hides the progress bar.
+ * This is the *sort of* the same as setting the status to 100%, with the
+ * difference being `done()` makes some placebo effect of some realistic motion.
+ *
+ * NProgress.done();
+ *
+ * If `true` is passed, it will show the progress bar even if its hidden.
+ *
+ * NProgress.done(true);
+ */
+f.done=function(e){return e||f.status?f.inc(.3+.5*Math.random()).set(1):this},
+/**
+ * Increments by a random amount.
+ */
+f.inc=function(e){var t=f.status;return t?t>1?void 0:("number"!=typeof e&&(e=t>=0&&t<.2?.1:t>=.2&&t<.5?.04:t>=.5&&t<.8?.02:t>=.8&&t<.99?.005:0),t=n(t+e,0,.994),f.set(t)):f.start()},f.trickle=function(){return f.inc()},
+/**
+ * Waits for all supplied jQuery promises and
+ * increases the progress as the promises resolve.
+ *
+ * @param $promise jQUery Promise
+ */
+c=0,l=0,f.promise=function(e){return e&&"resolved"!==e.state()?(0===l&&f.start(),c++,l++,e.always(function(){l--,0===l?(c=0,f.done()):f.set((c-l)/c)}),this):this},
+/**
+ * (Internal) renders the progress bar markup based on the `template`
+ * setting.
+ */
+f.render=function(n){if(f.isRendered())return document.getElementById("nprogress");o(document.documentElement,"nprogress-busy");var r=document.createElement("div");r.id="nprogress",r.innerHTML=d.template;var i,s=r.querySelector(d.barSelector),a=n?"-100":t(f.status||0),c=e(d.parent)?d.parent:document.querySelector(d.parent);return m(s,{transition:"all 0 linear",transform:"translate3d("+a+"%,0,0)"}),d.showSpinner||(i=r.querySelector(d.spinnerSelector),i&&u(i)),c!=document.body&&o(c,"nprogress-custom-parent"),c.appendChild(r),r},
+/**
+ * Removes the element. Opposite of render().
+ */
+f.remove=function(){s(document.documentElement,"nprogress-busy");var n=e(d.parent)?d.parent:document.querySelector(d.parent);s(n,"nprogress-custom-parent");var t=document.getElementById("nprogress");t&&u(t)},
+/**
+ * Checks if the progress bar is rendered.
+ */
+f.isRendered=function(){return!!document.getElementById("nprogress")},
+/**
+ * Determine which positioning CSS rule to use.
+ */
+f.getPositioningCSS=function(){
+// Sniff on document.body.style
+var e=document.body.style,n="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";
+// Sniff prefixes
+return n+"Perspective"in e?"translate3d":n+"Transform"in e?"translate":"margin"};var p=function(){function e(){var t=n.shift();t&&t(e)}var n=[];return function(t){n.push(t),1==n.length&&e()}}(),m=function(){function e(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(e,n){return n.toUpperCase()})}function n(e){var n=document.body.style;if(e in n)return e;for(var t,r=i.length,o=e.charAt(0).toUpperCase()+e.slice(1);r--;)if(t=i[r]+o,t in n)return t;return e}function t(t){return t=e(t),o[t]||(o[t]=n(t))}function r(e,n,r){n=t(n),e.style[n]=r}var i=["Webkit","O","Moz","ms"],o={};return function(e,n){var t,i,o=arguments;if(2==o.length)for(t in n)i=n[t],void 0!==i&&n.hasOwnProperty(t)&&r(e,t,i);else r(e,o[1],o[2])}}();
+/**
+ * (Internal) Applies css properties to an element, similar to the jQuery
+ * css method.
+ *
+ * While this helper does assist with vendor prefixed property names, it
+ * does not perform any manipulation of values prior to setting styles.
+ */return f});
\ No newline at end of file
diff --git a/scripts/vendor/waitMe.min.js b/scripts/vendor/waitMe.min.js
new file mode 100644
index 00000000..fdb67289
--- /dev/null
+++ b/scripts/vendor/waitMe.min.js
@@ -0,0 +1,13 @@
+/*
+waitMe - 1.19 [31.10.17]
+Author: vadimsva
+Github: https://github.com/vadimsva/waitMe
+*/
+(function(b){b.fn.waitMe=function(p){return this.each(function(){function y(){var a=f.attr("data-waitme_id");f.removeClass("waitMe_container").removeAttr("data-waitme_id");f.find('.waitMe[data-waitme_id="'+a+'"]').remove()}var f=b(this),z,g,e,r=!1,t="background-color",u="",q="",v,a,w,n={init:function(){function n(a){l.css({top:"auto",transform:"translateY("+a+"px) translateZ(0)"})}a=b.extend({effect:"bounce",text:"",bg:"rgba(255,255,255,0.7)",color:"#000",maxSize:"",waitTime:-1,textPos:"vertical",
+fontSize:"",source:"",onClose:function(){}},p);w=(new Date).getMilliseconds();v=b('');switch(a.effect){case "none":e=0;break;case "bounce":e=3;break;case "rotateplane":e=1;break;case "stretch":e=5;break;case "orbit":e=2;r=!0;break;case "roundBounce":e=12;break;case "win8":e=5;r=!0;break;case "win8_linear":e=5;r=!0;break;case "ios":e=12;break;case "facebook":e=3;break;case "rotation":e=1;t="border-color";break;case "timer":e=2;var c=b.isArray(a.color)?
+a.color[0]:a.color;u="border-color:"+c;break;case "pulse":e=1;t="border-color";break;case "progressBar":e=1;break;case "bouncePulse":e=3;break;case "img":e=1}""!==u&&(u+=";");if(0';else for(var d=1;d<=e;++d)b.isArray(a.color)?(c=a.color[d],void 0==c&&(c="#000")):c=a.color,q=r?q+('