From 6288112044f76a7beeb7666ae1169e0e1f1d4f7c Mon Sep 17 00:00:00 2001 From: RD WebDesign Date: Thu, 24 Feb 2022 17:18:57 -0300 Subject: [PATCH 1/9] Allow bulk deletion of warnings - add Datatables extension Select (CSS and js); - add Datatables extension Buttons (CSS and js); - allow selection of multiple warnings; - new button to select all; - new button to delete all selected warnings; - add icons for the buttons; - hide buttons if all messages were deleted; - update CSS themes to format the new items; - Fix: update icon count after warning deletion; - Fix: remove triangle icon after delete last warning; Signed-off-by: RD WebDesign --- messages.php | 3 +- scripts/pi-hole/js/footer.js | 20 +---- scripts/pi-hole/js/messages.js | 88 ++++++++++++++++++---- scripts/pi-hole/js/utils.js | 18 +++++ scripts/pi-hole/php/header.php | 3 + scripts/vendor/datatables.buttons.min.js | 51 +++++++++++++ scripts/vendor/datatables.select.min.js | 45 +++++++++++ style/pi-hole.css | 53 +++++++++++++ style/themes/default-dark.css | 4 + style/themes/default-darker.css | 1 + style/themes/default-light.css | 16 ++++ style/themes/lcars.css | 18 ++++- style/vendor/datatables_extensions.min.css | 15 ++++ 13 files changed, 302 insertions(+), 33 deletions(-) create mode 100644 scripts/vendor/datatables.buttons.min.js create mode 100644 scripts/vendor/datatables.select.min.js create mode 100644 style/vendor/datatables_extensions.min.css diff --git a/messages.php b/messages.php index 9fce17c5..d6c6b15a 100644 --- a/messages.php +++ b/messages.php @@ -23,6 +23,7 @@ ID +   Time Type Message @@ -31,7 +32,7 @@ Data3 Data4 Data5 - Action +   diff --git a/scripts/pi-hole/js/footer.js b/scripts/pi-hole/js/footer.js index 5e386622..b8ac4238 100644 --- a/scripts/pi-hole/js/footer.js +++ b/scripts/pi-hole/js/footer.js @@ -5,6 +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 */ //The following functions allow us to display time until pi-hole is enabled after disabling. //Works between all pages @@ -101,21 +102,6 @@ function piholeChange(action, duration) { } } -function checkMessages() { - $.getJSON("api_db.php?status", function (data) { - if ("message_count" in data && data.message_count > 0) { - var title = - data.message_count > 1 - ? "There are " + data.message_count + " warnings. Click for further details." - : "There is one warning. Click for further details."; - - $("#pihole-diagnosis").prop("title", title); - $("#pihole-diagnosis-count").text(data.message_count); - $("#pihole-diagnosis").removeClass("hidden"); - } - }); -} - function testCookies() { if (navigator.cookieEnabled) { return true; @@ -235,9 +221,9 @@ $(function () { initCPUtemp(); // Run check immediately after page loading ... - checkMessages(); + utils.checkMessages(); // ... and once again with five seconds delay - setTimeout(checkMessages, 5000); + setTimeout(utils.checkMessages, 5000); }); // Handle Enable/Disable diff --git a/scripts/pi-hole/js/messages.js b/scripts/pi-hole/js/messages.js index f95777f6..8bae1e78 100644 --- a/scripts/pi-hole/js/messages.js +++ b/scripts/pi-hole/js/messages.js @@ -142,6 +142,7 @@ $(function () { order: [[0, "asc"]], columns: [ { data: "id", visible: false }, + { data: null, visible: true, width: "15px" }, { data: "timestamp", width: "8%", render: renderTimestamp }, { data: "type", width: "8%" }, { data: "message", orderable: false, render: renderMessage }, @@ -150,9 +151,17 @@ $(function () { { data: "blob3", visible: false }, { data: "blob4", visible: false }, { data: "blob5", visible: false }, - { data: null, width: "80px", orderable: false }, + { data: null, width: "22px", orderable: false }, ], columnDefs: [ + { + targets: 1, + orderable: false, + className: "select-checkbox", + render: function () { + return ""; + }, + }, { targets: "_all", render: $.fn.dataTable.render.text(), @@ -160,6 +169,11 @@ $(function () { ], drawCallback: function () { $('button[id^="deleteMessage_"]').on("click", deleteMessage); + + // Hide buttons if all messages were deleted + var hasRows = this.api().rows({ filter: "applied" }).data().length > 0; + $(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden"); + // Remove visible dropdown to prevent orphaning $("body > .bootstrap-select.dropdown").remove(); }, @@ -168,16 +182,50 @@ $(function () { var button = '"; - $("td:eq(3)", row).html(button); + $("td:eq(4)", row).html(button); }, + select: { + style: "multi", + selector: "td:not(:last-child)", + info: false, + }, + buttons: [ + { + text: '', + titleAttr: "Select All", + className: "btn-sm datatable-bt", + action: function () { + table + .rows({ + page: "current", + }) + .select(); + }, + }, + { + text: '', + titleAttr: "Delete Selected", + className: "btn-sm datatable-bt", + action: function () { + // For each ".selected" row ... + $("tr.selected").each(function () { + // ... delete the row identified by "data-id". + delMsg($(this).attr("data-id")); + }); + }, + }, + ], dom: - "<'row'<'col-sm-12'f>>" + - "<'row'<'col-sm-4'l><'col-sm-8'p>>" + + "<'row'<'col-sm-6'l><'col-sm-6'f>>" + + "<'row'<'col-sm-3'B><'col-sm-9'p>>" + "<'row'<'col-sm-12'<'table-responsive'tr>>>" + - "<'row'<'col-sm-5'i><'col-sm-7'p>>", + "<'row'<'col-sm-3'B><'col-sm-9'p>>" + + "<'row'<'col-sm-12'i>>", lengthMenu: [ [10, 25, 50, 100, -1], [10, 25, 50, 100, "All"], @@ -198,7 +246,7 @@ $(function () { } // Reset visibility of ID and blob columns - var hiddenCols = [0, 4, 5, 6, 7, 8]; + var hiddenCols = [0, 5, 6, 7, 8, 9]; for (var key in hiddenCols) { if (Object.prototype.hasOwnProperty.call(hiddenCols, key)) { data.columns[hiddenCols[key]].visible = false; @@ -211,21 +259,31 @@ $(function () { }); }); +// Remove 'bnt-group' class from container, to avoid grouping +$.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons"; + function deleteMessage() { - var tr = $(this).closest("tr"); - var id = tr.attr("data-id"); + // Passes the button data-del-id attribute as ID + delMsg($(this).attr("data-del-id")); +} + +function delMsg(id) { + // Finding out which should be deleted, based on ID + var tr = $("tr[data-id='" + id + "']"); utils.disableAll(); utils.showAlert("info", "", "Deleting message with ID " + parseInt(id, 10), "..."); + $.ajax({ url: "scripts/pi-hole/php/message.php", method: "post", dataType: "json", data: { action: "delete_message", id: id, token: token }, - success: function (response) { + }) + .done(function (response) { utils.enableAll(); if (response.success) { - utils.showAlert("success", "far fa-trash-alt", "Successfully deleted message # ", id); + utils.showAlert("success", "far fa-trash-alt", "Successfully deleted message # " + id, ""); table.row(tr).remove().draw(false).ajax.reload(null, false); } else { utils.showAlert( @@ -235,8 +293,11 @@ function deleteMessage() { response.message ); } - }, - error: function (jqXHR, exception) { + }) + .done( + utils.checkMessages // Update icon warnings count + ) + .fail(function (jqXHR, exception) { utils.enableAll(); utils.showAlert( "error", @@ -245,6 +306,5 @@ function deleteMessage() { jqXHR.responseText ); console.log(exception); // eslint-disable-line no-console - }, - }); + }); } diff --git a/scripts/pi-hole/js/utils.js b/scripts/pi-hole/js/utils.js index b9e1a573..cf1b6968 100644 --- a/scripts/pi-hole/js/utils.js +++ b/scripts/pi-hole/js/utils.js @@ -360,6 +360,23 @@ function colorBar(percentage, total, cssClass) { return '
' + bar + "
"; } +function checkMessages() { + $.getJSON("api_db.php?status", function (data) { + if ("message_count" in data && data.message_count > 0) { + var title = + data.message_count > 1 + ? "There are " + data.message_count + " warnings. Click for further details." + : "There is one warning. Click for further details."; + + $("#pihole-diagnosis").prop("title", title); + $("#pihole-diagnosis-count").text(data.message_count); + $("#pihole-diagnosis").removeClass("hidden"); + } else { + $("#pihole-diagnosis").addClass("hidden"); + } + }); +} + window.utils = (function () { return { escapeHtml: escapeHtml, @@ -382,5 +399,6 @@ window.utils = (function () { addFromQueryLog: addFromQueryLog, addTD: addTD, colorBar: colorBar, + checkMessages: checkMessages, }; })(); diff --git a/scripts/pi-hole/php/header.php b/scripts/pi-hole/php/header.php index d8ad253b..195174eb 100644 --- a/scripts/pi-hole/php/header.php +++ b/scripts/pi-hole/php/header.php @@ -204,6 +204,7 @@ + @@ -223,6 +224,8 @@ + + diff --git a/scripts/vendor/datatables.buttons.min.js b/scripts/vendor/datatables.buttons.min.js new file mode 100644 index 00000000..9dfe24e5 --- /dev/null +++ b/scripts/vendor/datatables.buttons.min.js @@ -0,0 +1,51 @@ +/*! + Buttons for DataTables 1.7.1 + ©2016-2021 SpryMedia Ltd - datatables.net/license +*/ +(function(e){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(y){return e(y,window,document)}):"object"===typeof exports?module.exports=function(y,w){y||(y=window);w&&w.fn.dataTable||(w=require("datatables.net")(y,w).$);return e(w,y,y.document)}:e(jQuery,window,document)})(function(e,y,w,r){function B(a,b,c){e.fn.animate?a.stop().fadeIn(b,c):(a.css("display","block"),c&&c.call(a))}function C(a,b,c){e.fn.animate?a.stop().fadeOut(b,c):(a.css("display","none"),c&&c.call(a))} +function E(a,b){a=new q.Api(a);b=b?b:a.init().buttons||q.defaults.buttons;return(new t(a,b)).container()}var q=e.fn.dataTable,I=0,J=0,x=q.ext.buttons,t=function(a,b){if(!(this instanceof t))return function(c){return(new t(c,a)).container()};"undefined"===typeof b&&(b={});!0===b&&(b={});Array.isArray(b)&&(b={buttons:b});this.c=e.extend(!0,{},t.defaults,b);b.buttons&&(this.c.buttons=b.buttons);this.s={dt:new q.Api(a),buttons:[],listenKeys:"",namespace:"dtb"+I++};this.dom={container:e("<"+this.c.dom.container.tag+ +"/>").addClass(this.c.dom.container.className)};this._constructor()};e.extend(t.prototype,{action:function(a,b){a=this._nodeToButton(a);if(b===r)return a.conf.action;a.conf.action=b;return this},active:function(a,b){var c=this._nodeToButton(a);a=this.c.dom.button.active;c=e(c.node);if(b===r)return c.hasClass(a);c.toggleClass(a,b===r?!0:b);return this},add:function(a,b){var c=this.s.buttons;if("string"===typeof b){b=b.split("-");var d=this.s;c=0;for(var f=b.length-1;c"),k.conf._collection=k.collection,this._expandButton(k.buttons,k.conf.buttons,!0,d)),n.init&&n.init.call(f.button(k.node),f,e(k.node),n),h++)}}},_buildButton:function(a,b){var c=this.c.dom.button, +d=this.c.dom.buttonLiner,f=this.c.dom.collection,h=this.s.dt,m=function(p){return"function"===typeof p?p(h,k,a):p};b&&f.button&&(c=f.button);b&&f.buttonLiner&&(d=f.buttonLiner);if(a.available&&!a.available(h,a))return!1;var g=function(p,l,v,u){u.action.call(l.button(v),p,l,v,u);e(l.table().node()).triggerHandler("buttons-action.dt",[l.button(v),l,v,u])};f=a.tag||c.tag;var n=a.clickBlurs===r?!0:a.clickBlurs,k=e("<"+f+"/>").addClass(c.className).attr("tabindex",this.s.dt.settings()[0].iTabIndex).attr("aria-controls", +this.s.dt.table().node().id).on("click.dtb",function(p){p.preventDefault();!k.hasClass(c.disabled)&&a.action&&g(p,h,k,a);n&&k.trigger("blur")}).on("keyup.dtb",function(p){13===p.keyCode&&!k.hasClass(c.disabled)&&a.action&&g(p,h,k,a)});"a"===f.toLowerCase()&&k.attr("href","#");"button"===f.toLowerCase()&&k.attr("type","button");d.tag?(f=e("<"+d.tag+"/>").html(m(a.text)).addClass(d.className),"a"===d.tag.toLowerCase()&&f.attr("href","#"),k.append(f)):k.html(m(a.text));!1===a.enabled&&k.addClass(c.disabled); +a.className&&k.addClass(a.className);a.titleAttr&&k.attr("title",m(a.titleAttr));a.attr&&k.attr(a.attr);a.namespace||(a.namespace=".dt-button-"+J++);d=(d=this.c.dom.buttonContainer)&&d.tag?e("<"+d.tag+"/>").addClass(d.className).append(k):k;this._addKey(a);this.c.buttonCreated&&(d=this.c.buttonCreated(a,d));return{conf:a,node:k.get(0),inserter:d,buttons:[],inCollection:b,collection:null}},_nodeToButton:function(a,b){b||(b=this.s.buttons);for(var c=0,d=b.length;c").addClass("dt-button-collection").addClass(f.collectionLayout).css("display","none");a=e(a).addClass(f.contentClassName).attr("role","menu").appendTo(c);h.attr("aria-expanded","true");h.parents("body")[0]!==w.body&&(h=w.body.lastChild);f.collectionTitle&&c.prepend('
'+f.collectionTitle+"
");B(c.insertAfter(h),f.fade);d=e(b.table().container());var g=c.css("position");"dt-container"===f.align&&(h=h.parent(),c.css("width",d.width()));if("absolute"=== +g){var n=h.position();g=e(b.node()).position();c.css({top:g.top+h.outerHeight(),left:n.left});n=c.outerHeight();var k=d.offset().top+d.height();k=g.top+h.outerHeight()+n-k;var p=g.top-n,l=d.offset().top;g=g.top-n-5;(k>l-p||f.dropup)&&-gn+l&&(g-=n+l,d-=k+l,l=g>d?l+d:l+g)):(l=g-n,dd?l+d:l+g))):(d=h.offset().top,l=0,l="button-right"===f.align?v-k:p-n);c.css("left",c.position().left+l)}else d=c.height()/2,d>e(y).height()/2&&(d=e(y).height()/2),c.css("marginTop",-1*d);f.background&&t.background(!0,f.backgroundClassName,f.fade,h);e("div.dt-button-background").on("click.dtb-collection",function(){});e("body").on("click.dtb-collection",function(u){var z=e.fn.addBack?"addBack":"andSelf",F=e(u.target).parent()[0]; +(!e(u.target).parents()[z]().filter(a).length&&!e(F).hasClass("dt-buttons")||e(u.target).hasClass("dt-button-background"))&&m()}).on("keyup.dtb-collection",function(u){27===u.keyCode&&m()});f.autoClose&&setTimeout(function(){b.on("buttons-action.b-internal",function(u,z,F,K){K[0]!==h[0]&&m()})},0);e(c).trigger("buttons-popover.dt")}});t.background=function(a,b,c,d){c===r&&(c=400);d||(d=w.body);a?B(e("
").addClass(b).css("display","none").insertAfter(d),c):C(e("div."+b),c,function(){e(this).removeClass(b).remove()})}; +t.instanceSelector=function(a,b){if(a===r||null===a)return e.map(b,function(h){return h.inst});var c=[],d=e.map(b,function(h){return h.name}),f=function(h){if(Array.isArray(h))for(var m=0,g=h.length;m)<[^<]*)*<\/script>/gi,"");a=a.replace(//g,"");if(!b||b.stripHtml)a=a.replace(/<[^>]*>/g,"");if(!b||b.trim)a=a.replace(/^\s+|\s+$/g,"");if(!b||b.stripNewlines)a=a.replace(/\n/g," ");if(!b||b.decodeEntities)G.innerHTML=a,a=G.value;return a};t.defaults={buttons:["copy","excel","csv","pdf","print"],name:"main",tabIndex:0,dom:{container:{tag:"div",className:"dt-buttons"},collection:{tag:"div",className:""},button:{tag:"button",className:"dt-button", +active:"active",disabled:"disabled"},buttonLiner:{tag:"span",className:""}}};t.version="1.7.1";e.extend(x,{collection:{text:function(a){return a.i18n("buttons.collection","Collection")},className:"buttons-collection",init:function(a,b,c){b.attr("aria-expanded",!1)},action:function(a,b,c,d){a.stopPropagation();d._collection.parents("body").length?this.popover(!1,d):this.popover(d._collection,d)},attr:{"aria-haspopup":!0}},copy:function(a,b){if(x.copyHtml5)return"copyHtml5"},csv:function(a,b){if(x.csvHtml5&& +x.csvHtml5.available(a,b))return"csvHtml5"},excel:function(a,b){if(x.excelHtml5&&x.excelHtml5.available(a,b))return"excelHtml5"},pdf:function(a,b){if(x.pdfHtml5&&x.pdfHtml5.available(a,b))return"pdfHtml5"},pageLength:function(a){a=a.settings()[0].aLengthMenu;var b=[],c=[];if(Array.isArray(a[0]))b=a[0],c=a[1];else for(var d=0;d"+a+"":"";B(e('
').html(a).append(e("
")["string"===typeof b?"html":"append"](b)).css("display","none").appendTo("body"));c!==r&&0!== +c&&(A=setTimeout(function(){d.buttons.info(!1)},c));this.on("destroy.btn-info",function(){d.buttons.info(!1)});return this});q.Api.register("buttons.exportData()",function(a){if(this.context.length)return L(new q.Api(this.context[0]),a)});q.Api.register("buttons.exportInfo()",function(a){a||(a={});var b=a;var c="*"===b.filename&&"*"!==b.title&&b.title!==r&&null!==b.title&&""!==b.title?b.title:b.filename;"function"===typeof c&&(c=c());c===r||null===c?c=null:(-1!==c.indexOf("*")&&(c=c.replace("*",e("head > title").text()).trim()), +c=c.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g,""),(b=D(b.extension))||(b=""),c+=b);b=D(a.title);b=null===b?null:-1!==b.indexOf("*")?b.replace("*",e("head > title").text()||"Exported data"):b;return{filename:c,title:b,messageTop:H(this,a.message||a.messageTop,"top"),messageBottom:H(this,a.messageBottom,"bottom")}});var D=function(a){return null===a||a===r?null:"function"===typeof a?a():a},H=function(a,b,c){b=D(b);if(null===b)return null;a=e("caption",a.table().container()).eq(0);return"*"=== +b?a.css("caption-side")!==c?null:a.length?a.text():"":b},G=e("