mirror of
https://github.com/pi-hole/web.git
synced 2026-01-09 06:20:56 +08:00
Merge branch 'development-v6' into tweak/settings_changed_only
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
commit
52991f495c
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@ -24,12 +24,12 @@ jobs:
|
||||
uses: actions/checkout@v4.1.1
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: "javascript"
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v8.0.0
|
||||
- uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 30
|
||||
|
||||
2
.github/workflows/stale_pr.yml
vendored
2
.github/workflows/stale_pr.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v8.0.0
|
||||
- uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Do not automatically mark PR/issue as stale
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version: "20.x"
|
||||
cache: npm
|
||||
|
||||
2596
package-lock.json
generated
2596
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "AdminLTE",
|
||||
"name": "Pi-hole web interface",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
@ -27,9 +27,9 @@
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.16",
|
||||
"eslint-plugin-compat": "^4.2.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-cli": "^10.1.0",
|
||||
"prettier": "^3.1.0",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"prettier": "^3.1.1",
|
||||
"xo": "^0.56.0"
|
||||
},
|
||||
"browserslist": [
|
||||
@ -106,5 +106,24 @@
|
||||
"unicorn/no-negated-condition": "off"
|
||||
}
|
||||
},
|
||||
"dependencies": {}
|
||||
"dependencies": {
|
||||
"admin-lte": "2.4.18",
|
||||
"bootstrap-notify": "3.1.3",
|
||||
"bootstrap-select": "1.13.18",
|
||||
"bootstrap-toggle": "2.2.2",
|
||||
"chart.js": "4.4.1",
|
||||
"chartjs-adapter-moment": "1.0.1",
|
||||
"chartjs-plugin-deferred": "2.0.0",
|
||||
"chartjs-plugin-zoom": "2.0.1",
|
||||
"datatables.net-bs": "1.10.21",
|
||||
"datatables.net-buttons-bs": "1.7.1",
|
||||
"datatables.net-select-bs": "1.3.1",
|
||||
"daterangepicker": "3.1.0",
|
||||
"hammerjs": "2.0.8",
|
||||
"jquery": "3.7.1",
|
||||
"moment": "2.30.1",
|
||||
"myclabs.jquery.confirm": "2.7.0",
|
||||
"qrious": "4.0.2",
|
||||
"select2": "4.0.13"
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<div><input type="checkbox" id="disk"><label for="disk">Query on-disk data. This is <em>a lot</em> slower but necessary if you want to obtain queries older than 24 hours.</label></div>
|
||||
<div><input type="checkbox" id="disk"><label for="disk">Query on-disk data. This is <em>a lot</em> slower but necessary if you want to obtain queries older than 24 hours. This option disables live update.</label></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -152,7 +152,10 @@ mg.include('scripts/pi-hole/lua/header_authenticated.lp','r')
|
||||
<div class="box" id="recent-queries">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Recent Queries</h3>
|
||||
<a id="refresh" href="#" class="btn btn-sm btn-info btn-flat pull-right">Refresh</a>
|
||||
<div class="pull-right align-click-options">
|
||||
<span><input type="checkbox" id="live"><label for="live">Live update</label></span>
|
||||
<a id="refresh" href="#" class="btn btn-sm btn-info btn-flat">Refresh</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.box-header -->
|
||||
<div class="box-body">
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
const REFRESH_INTERVAL = {
|
||||
logs: 500, // 0.5 sec (logs page)
|
||||
summary: 1000, // 1 sec (dashboard)
|
||||
query_log: 2000, // 2 sec (Query Log)
|
||||
blocking: 10000, // 10 sec (all pages, sidebar)
|
||||
metrics: 10000, // 10 sec (settings page)
|
||||
system: 20000, // 20 sec (all pages, sidebar)
|
||||
|
||||
@ -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, groups:false,, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false */
|
||||
/* global utils:false, groups:false,, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
@ -277,10 +277,10 @@ function initTable() {
|
||||
var ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push($(this).attr("data-id"));
|
||||
ids.push({ item: $(this).attr("data-id") });
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
delItems(ids);
|
||||
delGroupItems("client", ids, table);
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -347,59 +347,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
|
||||
|
||||
function deleteClient() {
|
||||
// Passes the button data-id attribute as ID
|
||||
const ids = [$(this).attr("data-id")];
|
||||
delItems(ids);
|
||||
}
|
||||
|
||||
function delItems(ids) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
|
||||
// Get first element from array
|
||||
const clientRaw = ids[0];
|
||||
const client = utils.hexDecode(clientRaw);
|
||||
|
||||
// Remove first element from array
|
||||
ids.shift();
|
||||
|
||||
utils.disableAll();
|
||||
const idstring = ids.join(", ");
|
||||
utils.showAlert("info", "", "Deleting client...", client);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/clients/" + encodeURIComponent(client),
|
||||
method: "delete",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted client: ", client);
|
||||
table.row(clientRaw).remove().draw(false);
|
||||
if (ids.length > 0) {
|
||||
// Recursively delete all remaining items
|
||||
delItems(ids);
|
||||
return;
|
||||
}
|
||||
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of clients in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
"error",
|
||||
"",
|
||||
"Error while deleting client(s): " + idstring,
|
||||
data.responseText
|
||||
);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
const ids = [{ item: $(this).attr("data-id") }];
|
||||
delGroupItems("client", ids, table);
|
||||
}
|
||||
|
||||
function addClient() {
|
||||
|
||||
@ -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 apiFailure:false, utils:false, initTable:false */
|
||||
/* global apiFailure:false, utils:false, initTable:false, updateFtlInfo:false */
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var groups = [];
|
||||
@ -39,3 +39,51 @@ function processGroupResult(data, type, done, notDone) {
|
||||
utils.showAlert("error", "", `Error while ${notDone} ${type} ${error.item}`, error.error);
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function delGroupItems(type, ids, table) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
|
||||
const url = "/api/" + type + "s:batchDelete";
|
||||
|
||||
// use utils.hexDecode() to decode all clients
|
||||
let idstring = "";
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
ids[i].item = utils.hexDecode(ids[i].item);
|
||||
idstring += ids[i].item + ", ";
|
||||
}
|
||||
|
||||
// Remove last comma and space from idstring
|
||||
idstring = idstring.substring(0, idstring.length - 2);
|
||||
|
||||
// Append "s" to type if more than one item is deleted
|
||||
type += ids.length > 1 ? "s" : "";
|
||||
|
||||
utils.disableAll();
|
||||
utils.showAlert("info", "", "Deleting " + ids.length + " " + type + "...", idstring);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
data: JSON.stringify(ids),
|
||||
method: "POST",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted " + type, idstring);
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of <type> items in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while deleting " + type, data.responseText);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
}
|
||||
|
||||
@ -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, groups:false,, getGroups:false, updateFtlInfo:false, apiFailure:false, processGroupResult:false */
|
||||
/* global utils:false, groups:false,, getGroups:false, updateFtlInfo:false, apiFailure:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
@ -333,7 +333,7 @@ function initTable() {
|
||||
ids.push($(this).attr("data-id"));
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
delItems(ids);
|
||||
deleteDomains(ids);
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -430,60 +430,22 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
|
||||
|
||||
function deleteDomain() {
|
||||
// Passes the button data-id attribute as ID
|
||||
const ids = [$(this).attr("data-id")];
|
||||
delItems(ids);
|
||||
deleteDomains([$(this).attr("data-id")]);
|
||||
}
|
||||
|
||||
function delItems(ids) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
function deleteDomains(encodedIds) {
|
||||
const decodedIds = [];
|
||||
for (let i = 0; i < encodedIds.length; i++) {
|
||||
// Decode domain, type, and kind and add to array
|
||||
const parts = encodedIds[i].split("_");
|
||||
decodedIds[i] = {
|
||||
item: parts[0],
|
||||
type: parts[1],
|
||||
kind: parts[2],
|
||||
};
|
||||
}
|
||||
|
||||
// Get first element from array
|
||||
const domainRaw = ids[0];
|
||||
const domain = utils.hexDecode(domainRaw.split("_")[0]);
|
||||
const typestr = $("#old_type_" + domainRaw).val();
|
||||
|
||||
// Remove first element from array
|
||||
ids.shift();
|
||||
|
||||
utils.disableAll();
|
||||
const idstring = ids.join(", ");
|
||||
utils.showAlert("info", "", "Deleting domain...", domain);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/domains/" + typestr + "/" + encodeURIComponent(domain),
|
||||
method: "delete",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted domain: ", domain);
|
||||
table.row(domainRaw).remove().draw(false);
|
||||
if (ids.length > 0) {
|
||||
// Recursively delete all remaining items
|
||||
delItems(ids);
|
||||
return;
|
||||
}
|
||||
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of lists in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert(
|
||||
"error",
|
||||
"",
|
||||
"Error while deleting domain(s): " + idstring,
|
||||
data.responseText
|
||||
);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
delGroupItems("domain", decodedIds, table);
|
||||
}
|
||||
|
||||
function addDomain() {
|
||||
|
||||
@ -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, groups:false, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false */
|
||||
/* global utils:false, groups:false, apiFailure:false, updateFtlInfo:false, getGroups:false, processGroupResult:false, delGroupItems:false */
|
||||
/* exported initTable */
|
||||
|
||||
var table;
|
||||
@ -397,10 +397,10 @@ function initTable() {
|
||||
var ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push($(this).attr("data-id"));
|
||||
ids.push({ item: $(this).attr("data-id") });
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
delItems(ids);
|
||||
delGroupItems("list", ids, table);
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -485,54 +485,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
|
||||
|
||||
function deleteList() {
|
||||
// Passes the button data-id attribute as ID
|
||||
const ids = [$(this).attr("data-id")];
|
||||
delItems(ids);
|
||||
}
|
||||
|
||||
function delItems(ids) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
|
||||
// Get first element from array
|
||||
const addressRaw = ids[0];
|
||||
const address = utils.hexDecode(addressRaw);
|
||||
|
||||
// Remove first element from array
|
||||
ids.shift();
|
||||
|
||||
utils.disableAll();
|
||||
const idstring = ids.join(", ");
|
||||
utils.showAlert("info", "", "Deleting list(s) ...", address);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/lists/" + encodeURIComponent(address),
|
||||
method: "delete",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted list: ", address);
|
||||
table.row(addressRaw).remove().draw(false);
|
||||
if (ids.length > 0) {
|
||||
// Recursively delete all remaining items
|
||||
delItems(ids);
|
||||
return;
|
||||
}
|
||||
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of lists in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while deleting list(s): " + idstring, data.responseText);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
const ids = [{ item: $(this).attr("data-id") }];
|
||||
delGroupItems("list", ids, table);
|
||||
}
|
||||
|
||||
function addList(event) {
|
||||
|
||||
@ -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, apiFailure:false, updateFtlInfo:false, processGroupResult:false */
|
||||
/* global utils:false, apiFailure:false, updateFtlInfo:false, processGroupResult:false, delGroupItems:false */
|
||||
|
||||
var table,
|
||||
idNames = {};
|
||||
@ -56,14 +56,15 @@ $(function () {
|
||||
],
|
||||
drawCallback: function () {
|
||||
// Hide buttons if all groups were deleted
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 0;
|
||||
// if there is one row, it's the default group
|
||||
var hasRows = this.api().rows({ filter: "applied" }).data().length > 1;
|
||||
$(".datatable-bt").css("visibility", hasRows ? "visible" : "hidden");
|
||||
|
||||
$('button[id^="deleteGroup_"]').on("click", deleteGroup);
|
||||
},
|
||||
rowCallback: function (row, data) {
|
||||
idNames[data.id] = data.name;
|
||||
$(row).attr("data-id", data.id);
|
||||
var dataId = utils.hexEncode(data.name);
|
||||
$(row).attr("data-id", dataId);
|
||||
var tooltip =
|
||||
"Added: " +
|
||||
utils.datetime(data.date_added, false) +
|
||||
@ -102,12 +103,13 @@ $(function () {
|
||||
commentEl.on("change", editGroup);
|
||||
|
||||
$("td:eq(4)", row).empty();
|
||||
// Show delete button for all but the default group
|
||||
if (data.id !== 0) {
|
||||
var button =
|
||||
'<button type="button" class="btn btn-danger btn-xs" id="deleteGroup_' +
|
||||
data.id +
|
||||
'" data-del-id="' +
|
||||
data.id +
|
||||
dataId +
|
||||
'" data-id="' +
|
||||
dataId +
|
||||
'">' +
|
||||
'<span class="far fa-trash-alt"></span>' +
|
||||
"</button>";
|
||||
@ -151,10 +153,10 @@ $(function () {
|
||||
var ids = [];
|
||||
$("tr.selected").each(function () {
|
||||
// ... add the row identified by "data-id".
|
||||
ids.push(parseInt($(this).attr("data-id"), 10));
|
||||
ids.push({ item: $(this).attr("data-id") });
|
||||
});
|
||||
// Delete all selected rows at once
|
||||
delItems(ids);
|
||||
delGroupItems("group", ids, table);
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -198,6 +200,11 @@ $(function () {
|
||||
}
|
||||
|
||||
table.on("init select deselect", function () {
|
||||
// if the Default group is selected, undo the selection of it
|
||||
if (table.rows({ selected: true }).data().pluck("id").indexOf(0) !== -1) {
|
||||
table.rows(0).deselect();
|
||||
}
|
||||
|
||||
utils.changeBulkDeleteStates(table);
|
||||
});
|
||||
|
||||
@ -221,59 +228,8 @@ $.fn.dataTable.Buttons.defaults.dom.container.className = "dt-buttons";
|
||||
|
||||
function deleteGroup() {
|
||||
// Passes the button data-del-id attribute as ID
|
||||
const ids = [parseInt($(this).attr("data-del-id"), 10)];
|
||||
delItems(ids);
|
||||
}
|
||||
|
||||
function delItems(ids) {
|
||||
// Check input validity
|
||||
if (!Array.isArray(ids)) return;
|
||||
|
||||
for (const id of ids) {
|
||||
// Exploit prevention: Return early for non-numeric IDs
|
||||
if (typeof id !== "number") return;
|
||||
}
|
||||
|
||||
// Get first element from array
|
||||
const id = ids[0];
|
||||
const name = idNames[id];
|
||||
|
||||
// Remove first element from array
|
||||
ids.shift();
|
||||
|
||||
utils.disableAll();
|
||||
const idstring = ids.join(", ");
|
||||
utils.showAlert("info", "", "Deleting group...", name);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/groups/" + name,
|
||||
method: "delete",
|
||||
})
|
||||
.done(function () {
|
||||
utils.enableAll();
|
||||
utils.showAlert("success", "far fa-trash-alt", "Successfully deleted group: ", name);
|
||||
table.row(id).remove().draw(false);
|
||||
if (ids.length > 0) {
|
||||
// Recursively delete all remaining items
|
||||
delItems(ids);
|
||||
return;
|
||||
}
|
||||
|
||||
table.ajax.reload(null, false);
|
||||
|
||||
// Clear selection after deletion
|
||||
table.rows().deselect();
|
||||
utils.changeBulkDeleteStates(table);
|
||||
|
||||
// Update number of groups in the sidebar
|
||||
updateFtlInfo();
|
||||
})
|
||||
.fail(function (data, exception) {
|
||||
apiFailure(data);
|
||||
utils.enableAll();
|
||||
utils.showAlert("error", "", "Error while deleting group(s): " + idstring, data.responseText);
|
||||
console.log(exception); // eslint-disable-line no-console
|
||||
});
|
||||
const ids = [{ item: $(this).attr("data-id") }];
|
||||
delGroupItems("group", ids, table);
|
||||
}
|
||||
|
||||
function addGroup() {
|
||||
|
||||
@ -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, utils:false */
|
||||
/* global moment:false, utils:false, REFRESH_INTERVAL:false */
|
||||
|
||||
const beginningOfTime = 1262304000; // Jan 01 2010, 00:00 in seconds
|
||||
const endOfTime = 2147483647; // Jan 19, 2038, 03:14 in seconds
|
||||
@ -263,7 +263,7 @@ function formatInfo(data) {
|
||||
|
||||
// Parse Query Status
|
||||
var queryStatus = parseQueryStatus(data);
|
||||
var divStart = '<div class="col-xl-2 col-lg-4 col-md-6 col-12">';
|
||||
var divStart = '<div class="col-xl-2 col-lg-4 col-md-6 col-12 overflow-wrap">';
|
||||
var statusInfo = "";
|
||||
if (queryStatus.colorClass !== false) {
|
||||
statusInfo =
|
||||
@ -282,7 +282,7 @@ function formatInfo(data) {
|
||||
var regexLink =
|
||||
'<a href="groups/domains?domainid=' +
|
||||
data.regex_id +
|
||||
'" target="_blank">regex ID ' +
|
||||
'" target="_blank">entry with ID ' +
|
||||
data.regex_id +
|
||||
"</a>";
|
||||
regexInfo =
|
||||
@ -470,6 +470,19 @@ function getAPIURL(filters) {
|
||||
return encodeURI(apiurl);
|
||||
}
|
||||
|
||||
var liveMode = false;
|
||||
$("#live").prop("checked", liveMode);
|
||||
$("#live").on("click", function () {
|
||||
liveMode = $(this).prop("checked");
|
||||
liveUpdate();
|
||||
});
|
||||
|
||||
function liveUpdate() {
|
||||
if (liveMode) {
|
||||
refreshTable();
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
// Do we want to filter queries?
|
||||
var GETDict = utils.parseQueryString();
|
||||
@ -512,6 +525,10 @@ $(function () {
|
||||
dataFilter: function (d) {
|
||||
var json = jQuery.parseJSON(d);
|
||||
cursor = json.cursor; // Extract cursor from original data
|
||||
if (liveMode) {
|
||||
utils.setTimer(liveUpdate, REFRESH_INTERVAL.query_log);
|
||||
}
|
||||
|
||||
return d;
|
||||
},
|
||||
},
|
||||
@ -521,7 +538,7 @@ $(function () {
|
||||
"<'row'<'col-sm-12'<'table-responsive'tr>>>" +
|
||||
"<'row'<'col-sm-5'i><'col-sm-7'p>>",
|
||||
autoWidth: false,
|
||||
processing: true,
|
||||
processing: false,
|
||||
order: [[0, "desc"]],
|
||||
columns: [
|
||||
{
|
||||
@ -639,6 +656,17 @@ $(function () {
|
||||
});
|
||||
|
||||
$("#refresh").on("click", refreshTable);
|
||||
|
||||
// Disable live mode when #disk is checked
|
||||
$("#disk").on("click", function () {
|
||||
if ($(this).prop("checked")) {
|
||||
$("#live").prop("checked", false);
|
||||
$("#live").prop("disabled", true);
|
||||
liveMode = false;
|
||||
} else {
|
||||
$("#live").prop("disabled", false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function refreshTable() {
|
||||
|
||||
@ -326,11 +326,6 @@ function createDynamicConfigTabs() {
|
||||
});
|
||||
$("#advanced-overlay").hide();
|
||||
|
||||
$("#advanced-content").append(
|
||||
'<div class="col-lg-12 settings-level-expert">' +
|
||||
'<button type="button" class="btn btn-primary save-button" id="save"><i class="fa-solid fa-fw fa-floppy-disk"></i> Save & Apply</button>' +
|
||||
"</div>"
|
||||
);
|
||||
$("button[id='save']").on("click", function () {
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
@ -20,7 +20,7 @@ const fadeIn = true;
|
||||
const markUpdates = true;
|
||||
|
||||
// Format a line of the dnsmasq log
|
||||
function formatLine(line) {
|
||||
function formatDnsmasq(line) {
|
||||
// Remove dnsmasq + PID
|
||||
let txt = line.replaceAll(/ dnsmasq\[\d*]/g, "");
|
||||
|
||||
@ -38,6 +38,36 @@ function formatLine(line) {
|
||||
return txt;
|
||||
}
|
||||
|
||||
function formatFTL(line, prio) {
|
||||
// Colorize priority
|
||||
let prioClass = "";
|
||||
switch (prio) {
|
||||
case "INFO": {
|
||||
prioClass = "text-success";
|
||||
break;
|
||||
}
|
||||
|
||||
case "WARNING": {
|
||||
prioClass = "text-warning";
|
||||
break;
|
||||
}
|
||||
|
||||
case "ERR":
|
||||
case "EMERG":
|
||||
case "ALERT":
|
||||
case "CRIT": {
|
||||
prioClass = "text-danger";
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
prioClass = prio.startsWith("DEBUG") ? "text-info" : "text-muted";
|
||||
}
|
||||
|
||||
// Return formatted line
|
||||
return `<span class="${prioClass}">${utils.escapeHtml(prio)}</span> ${line}`;
|
||||
}
|
||||
|
||||
// Function that asks the API for new data
|
||||
function getData() {
|
||||
// Only update when spinner is spinning
|
||||
@ -61,7 +91,7 @@ function getData() {
|
||||
// Check if we have a new PID -> FTL was restarted
|
||||
if (lastPID !== data.pid) {
|
||||
if (lastPID !== -1) {
|
||||
$("#output").append("<i class='text-danger'>*** FTL restarted ***</i><br>");
|
||||
$("#output").append("<div><i class='text-danger'>*** FTL restarted ***</i></div>");
|
||||
}
|
||||
|
||||
// Remember PID
|
||||
@ -76,7 +106,7 @@ function getData() {
|
||||
// Set placeholder text if log file is empty and we have no new lines
|
||||
if (data.log.length === 0) {
|
||||
if (nextID === 0) {
|
||||
$("#output").html("<i>*** Log file is empty ***</i>");
|
||||
$("#output").html("<div><i>*** Log file is empty ***</i></div>");
|
||||
}
|
||||
|
||||
utils.setTimer(getData, REFRESH_INTERVAL.logs);
|
||||
@ -90,16 +120,19 @@ function getData() {
|
||||
}
|
||||
|
||||
data.log.forEach(function (line) {
|
||||
// Format line if this is the dnsmasq log
|
||||
if (GETDict.file === "dnsmasq") line.message = formatLine(line.message);
|
||||
// Escape HTML
|
||||
line.message = utils.escapeHtml(line.message);
|
||||
// Format line if applicable
|
||||
if (GETDict.file === "dnsmasq") line.message = formatDnsmasq(line.message);
|
||||
else if (GETDict.file === "ftl") line.message = formatFTL(line.message, line.prio);
|
||||
|
||||
// Add new line to output
|
||||
$("#output").append(
|
||||
'<span class="log-entry"><span class="text-muted">' +
|
||||
'<div class="log-entry"><span class="text-muted">' +
|
||||
moment(1000 * line.timestamp).format("YYYY-MM-DD HH:mm:ss.SSS") +
|
||||
"</span> " +
|
||||
line.message +
|
||||
"<br></span>"
|
||||
"</div>"
|
||||
);
|
||||
if (fadeIn) {
|
||||
//$(".left-line:last").fadeOut(2000);
|
||||
@ -137,7 +170,22 @@ function getData() {
|
||||
var gAutoScrolling = true;
|
||||
$("#output").on("scroll", function () {
|
||||
// Check if we are at the bottom of the output
|
||||
if ($("#output").scrollTop() + $("#output").innerHeight() >= $("#output")[0].scrollHeight) {
|
||||
//
|
||||
// - $("#output")[0].scrollHeight: This gets the entire height of the content
|
||||
// of the "output" element, including the part that is not visible due to
|
||||
// scrolling.
|
||||
// - $("#output").innerHeight(): This gets the inner height of the "output"
|
||||
// element, which is the visible part of the content.
|
||||
// - $("#output").scrollTop(): This gets the number of pixels that the content
|
||||
// of the "output" element is scrolled vertically from the top.
|
||||
//
|
||||
// By subtracting the inner height and the scroll top from the scroll height,
|
||||
// you get the distance from the bottom of the scrollable area.
|
||||
const bottom =
|
||||
$("#output")[0].scrollHeight - $("#output").innerHeight() - $("#output").scrollTop();
|
||||
// Add a tolerance of four line heights
|
||||
const tolerance = 4 * parseFloat($("#output").css("line-height"));
|
||||
if (bottom <= tolerance) {
|
||||
// Auto-scrolling is enabled
|
||||
gAutoScrolling = true;
|
||||
$("#autoscrolling").addClass("fa-check");
|
||||
|
||||
@ -561,35 +561,35 @@ function listAlert(type, items, data) {
|
||||
// Show a list of successful items if there are any
|
||||
if (data.processed.success.length > 0) {
|
||||
message +=
|
||||
"<strong>Successfully added " +
|
||||
"Successfully added " +
|
||||
data.processed.success.length +
|
||||
" " +
|
||||
type +
|
||||
(data.processed.success.length !== 1 ? "s" : "") +
|
||||
":</strong>";
|
||||
":";
|
||||
|
||||
// Loop over data.processed.success and print "item"
|
||||
for (const item in data.processed.success) {
|
||||
if (Object.prototype.hasOwnProperty.call(data.processed.success, item)) {
|
||||
message += "<br>- <strong>" + data.processed.success[item].item + "</strong>";
|
||||
message += "\n- " + data.processed.success[item].item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a line break if there are both successful and failed items
|
||||
if (data.processed.success.length > 0 && data.processed.errors.length > 0) {
|
||||
message += "<br><br>";
|
||||
message += "\n\n";
|
||||
}
|
||||
|
||||
// Show a list of failed items if there are any
|
||||
if (data.processed.errors.length > 0) {
|
||||
message +=
|
||||
"<strong>Failed to add " +
|
||||
"Failed to add " +
|
||||
data.processed.errors.length +
|
||||
" " +
|
||||
type +
|
||||
(data.processed.errors.length !== 1 ? "s" : "") +
|
||||
":</strong>\n";
|
||||
":\n";
|
||||
|
||||
// Loop over data.processed.errors and print "item: error"
|
||||
for (const item in data.processed.errors) {
|
||||
@ -600,7 +600,7 @@ function listAlert(type, items, data) {
|
||||
error = "Already present";
|
||||
}
|
||||
|
||||
message += "<br>- <strong>" + data.processed.errors[item].item + "</strong>: " + error;
|
||||
message += "\n- " + data.processed.errors[item].item + ": " + error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ mg.include('header.lp','r')
|
||||
<script src="<?=pihole.fileversion('scripts/vendor/datatables.min.js')?>"></script>
|
||||
<script src="<?=pihole.fileversion('scripts/vendor/datatables.select.min.js')?>"></script>
|
||||
<script src="<?=pihole.fileversion('scripts/vendor/datatables.buttons.min.js')?>"></script>
|
||||
<script src="<?=pihole.fileversion('scripts/vendor/chart.umd.min.js')?>"></script>
|
||||
<script src="<?=pihole.fileversion('scripts/vendor/chart.umd.js')?>"></script>
|
||||
<script src="<?=pihole.fileversion('scripts/vendor/chartjs-plugin-deferred.min.js')?>"></script>
|
||||
<script src="<?=pihole.fileversion('scripts/vendor/moment.min.js')?>"></script>
|
||||
<script src="<?=pihole.fileversion('scripts/vendor/chartjs-adapter-moment.js')?>"></script>
|
||||
|
||||
4
scripts/vendor/bootstrap-select.min.js
vendored
4
scripts/vendor/bootstrap-select.min.js
vendored
File diff suppressed because one or more lines are too long
2
scripts/vendor/bootstrap-select.min.js.map
vendored
2
scripts/vendor/bootstrap-select.min.js.map
vendored
File diff suppressed because one or more lines are too long
14
scripts/vendor/chart.umd.js
vendored
Normal file
14
scripts/vendor/chart.umd.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
scripts/vendor/chart.umd.js.map
vendored
2
scripts/vendor/chart.umd.js.map
vendored
File diff suppressed because one or more lines are too long
7
scripts/vendor/chart.umd.min.js
vendored
7
scripts/vendor/chart.umd.min.js
vendored
File diff suppressed because one or more lines are too long
4
scripts/vendor/jquery.min.js
vendored
4
scripts/vendor/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
2
scripts/vendor/jquery.min.js.map
vendored
2
scripts/vendor/jquery.min.js.map
vendored
File diff suppressed because one or more lines are too long
3
scripts/vendor/moment.min.js
vendored
3
scripts/vendor/moment.min.js
vendored
File diff suppressed because one or more lines are too long
1
scripts/vendor/moment.min.js.map
vendored
Normal file
1
scripts/vendor/moment.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -18,7 +18,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
||||
<div class="overlay" id="advanced-overlay">
|
||||
<i class="fa fa-sync fa-spin"></i>
|
||||
</div>
|
||||
<div class="col-lg-12 settings-level-expert">
|
||||
<div class="col-lg-12 settings-level-expert save-button-container">
|
||||
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i> Save & Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -177,7 +177,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 settings-level-basic">
|
||||
<div class="col-lg-12 settings-level-basic save-button-container">
|
||||
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i> Save & Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -200,7 +200,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 settings-level-basic">
|
||||
<div class="col-lg-12 settings-level-basic save-button-container">
|
||||
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i> Save & Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -258,7 +258,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 settings-level-basic">
|
||||
<div class="col-lg-12 settings-level-basic save-button-container">
|
||||
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i> Save & Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -103,7 +103,7 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 settings-level-basic">
|
||||
<div class="col-lg-12 settings-level-basic save-button-container">
|
||||
<button type="button" class="btn btn-primary save-button"><i class="fa-solid fa-fw fa-floppy-disk"></i> Save & Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -70,67 +70,67 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-striped table-bordered nowrap">
|
||||
<tbody>
|
||||
<tr>
|
||||
<tr title="Client broadcast to locate available servers">
|
||||
<th scope="row">
|
||||
<span>DHCPDISCOVER:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-discover"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Server to client in response to DHCPDISCOVER with offer of configuration parameters">
|
||||
<th scope="row">
|
||||
<span>DHCPOFFER:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-offer"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Client message to servers either 
 (a) requesting offered parameters from one server and implicitly declining offers from all others, 
 (b) confirming correctness of previously allocated address after, e.g., system reboot, or 
 (c) extending the lease on a particular network address">
|
||||
<th scope="row">
|
||||
<span>DHCPREQUEST:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-request"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Server to client with configuration parameters, including committed network address">
|
||||
<th scope="row">
|
||||
<span>DHCPACK:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-ack"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Server to client indicating client's notion of network address is incorrect (e.g., client has moved to new subnet) or client's lease as expired">
|
||||
<th scope="row">
|
||||
<span>DHCPNAK:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-nak"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Client to server indicating network address is already in use">
|
||||
<th scope="row">
|
||||
<span>DHCPDECLINE:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-decline"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Client to server, asking only for local configuration parameters; client already has externally configured network address">
|
||||
<th scope="row">
|
||||
<span>DHCPINFORM:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-inform"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Client to server relinquishing network address and cancelling remaining lease">
|
||||
<th scope="row">
|
||||
<span>DHCPRELEASE:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-release"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Could not provide an answer, e.g., because there are no leases left, the client wants to renew a lease that is outside of our range, or the explicitly requested address is already in use">
|
||||
<th scope="row">
|
||||
<span>DHCPNOANSWER:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-noanswer"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Processed BOOTP packets">
|
||||
<th scope="row">
|
||||
<span>BOOTP:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dhcp-bootp"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Processed PXE packets">
|
||||
<th scope="row">
|
||||
<span>PXE:</span>
|
||||
</th>
|
||||
@ -172,39 +172,39 @@ mg.include('scripts/pi-hole/lua/settings_header.lp','r')
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-striped table-bordered nowrap">
|
||||
<tbody>
|
||||
<tr>
|
||||
<tr title="Total size of the DNS cache">
|
||||
<th scope="row">
|
||||
<span title="Total size of the DNS cache">DNS cache size:</span>
|
||||
<span>DNS cache size:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dns-cache-size"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Share of the allocated cache that is currently in use (this includes stale/expired entries). If the percentage value is very low, it is advisable to reduce the DNS cache size to optimize performance">
|
||||
<th scope="row">
|
||||
<span title="Share of the allocated cache that is currently in use (this includes stale/expired entries). If the percentage value is very low, it is advisable to reduce the DNS cache size to optimize performance">Active cache records:</span>
|
||||
<span>Active cache records:</span>
|
||||
</th>
|
||||
<td id="cache-utilization"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Number of cache insertions, this number will grow continuously as expiring records naturally make space for new records">
|
||||
<th scope="row">
|
||||
<span title="Number of cache insertions, this number will grow continuously as expiring records naturally make space for new records">Total cache insertions:</span>
|
||||
<span>Total cache insertions:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dns-cache-inserted"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Number of cache entries that had to be removed although they are not expired. When this number if larger than zero, you should consider increasing your total cache size" lookatme-text="DNS cache evictions:">
|
||||
<th scope="row">
|
||||
<span title="Number of cache entries that had to be removed although they are not expired. When this number if larger than zero, you should consider increasing your total cache size" lookatme-text="DNS cache evictions:">DNS cache evictions:</span>
|
||||
<span>DNS cache evictions:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dns-cache-evicted"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Number of expired cache entries (they can still be used by the cache optimizer). These entries will only be freed when space is really needed (for efficiency reasons)">
|
||||
<th scope="row">
|
||||
<span title="Number of expired cache entries (they can still be used by the cache optimizer). These entries will only be freed when space is really needed (for efficiency reasons)">Expired DNS cache entries:</span>
|
||||
<span>Expired DNS cache entries:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dns-cache-expired"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr title="Number of immortal cache entries that will never expire (e.g. from /etc/hosts or local configuration)">
|
||||
<th scope="row">
|
||||
<span title="Number of immortal cache entries that will never expire (e.g. from /etc/hosts or local configuration)">Immortal DNS cache entries:</span>
|
||||
<span>Immortal DNS cache entries:</span>
|
||||
</th>
|
||||
<td id="sysinfo-dns-cache-immortal"> </td>
|
||||
</tr>
|
||||
|
||||
@ -5,6 +5,14 @@
|
||||
* This file is copyright under the latest version of the EUPL.
|
||||
* Please see LICENSE file for your rights under this license. */
|
||||
|
||||
:root {
|
||||
/* Datatables Select - bgcolor */
|
||||
--datatable-bgcolor: #e0e8ee;
|
||||
|
||||
/* Used in settings save button */
|
||||
--overlay-bgcolor: rgba(212, 220, 232, 0.55);
|
||||
}
|
||||
|
||||
.layout-boxed {
|
||||
background: url("../img/boxed-bg.png") repeat fixed;
|
||||
}
|
||||
@ -1005,11 +1013,6 @@ body:not(.lcars) .filter_types [class*="icheck-"] > input:first-child:checked +
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/* Datatables Select - bgcolor */
|
||||
:root {
|
||||
--datatable-bgcolor: #e0e8ee;
|
||||
}
|
||||
|
||||
div.dt-buttons {
|
||||
margin: 2px 0 5px;
|
||||
}
|
||||
@ -1065,6 +1068,7 @@ table.dataTable tbody > tr > .selected {
|
||||
|
||||
#output {
|
||||
position: relative;
|
||||
display: grid;
|
||||
margin: 5px 0;
|
||||
min-height: 36px;
|
||||
padding: 4px 8px;
|
||||
@ -1115,13 +1119,26 @@ table.dataTable tbody > tr > .selected {
|
||||
}
|
||||
|
||||
.pre-scrollable {
|
||||
max-height: calc(100vh - 300px);
|
||||
/* use dynamic unit to account for mobile browser interface height */
|
||||
max-height: max(195px, 100dvh - 170px);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
@media screen and (max-width: 767px) {
|
||||
.pre-scrollable {
|
||||
/* reduce max-height on mobile (higher header) */
|
||||
max-height: max(195px, 100dvh - 220px);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 546px) {
|
||||
.pre-scrollable {
|
||||
/* reduce max-height even more (box-header text wraps, increasing the height) */
|
||||
max-height: max(195px, 100dvh - 240px);
|
||||
}
|
||||
}
|
||||
|
||||
.hr-small {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
width: 100%;
|
||||
margin: 0 0 -1px;
|
||||
border-top: 1px solid #ff0000aa;
|
||||
}
|
||||
|
||||
@ -1141,11 +1158,23 @@ table.dataTable tbody > tr > .selected {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
.save-button-container {
|
||||
position: fixed;
|
||||
bottom: 2%;
|
||||
right: 2%;
|
||||
bottom: 1.2em;
|
||||
right: 0.6em;
|
||||
z-index: 10;
|
||||
background: var(--overlay-bgcolor);
|
||||
width: fit-content;
|
||||
border-radius: 0.5em;
|
||||
padding: 0;
|
||||
}
|
||||
.save-button {
|
||||
margin: 1.2em;
|
||||
}
|
||||
@media (min-width: 1250px) {
|
||||
.layout-boxed .save-button-container {
|
||||
right: calc((100% - 1250px) / 2 + 1.2em);
|
||||
}
|
||||
}
|
||||
|
||||
.settings-level-basic {
|
||||
@ -1423,3 +1452,15 @@ table.dataTable tbody > tr > .selected {
|
||||
.log-entry {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.align-click-options {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-column-gap: 15px;
|
||||
}
|
||||
|
||||
.overflow-wrap {
|
||||
overflow-wrap: break-word;
|
||||
inline-size: auto;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
:root {
|
||||
--datatable-bgcolor: rgba(64, 76, 88, 0.8);
|
||||
--overlay-bgcolor: var(--datatable-bgcolor);
|
||||
}
|
||||
|
||||
/* fix #2554: browser not detecting dark mode */
|
||||
|
||||
@ -109,6 +109,7 @@ select:-webkit-autofill {
|
||||
--darkreader-selection-background: #004daa;
|
||||
--darkreader-selection-text: #e8e6e3;
|
||||
--datatable-bgcolor: rgb(35, 47, 52);
|
||||
--overlay-bgcolor: rgba(35, 47, 52, 0.7);
|
||||
}
|
||||
|
||||
/* Modified CSS */
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
--blocked-color: var(--danger-color);
|
||||
|
||||
--datatable-bgcolor: rgba(18, 44, 68, 0.3);
|
||||
--overlay-bgcolor: rgba(24, 28, 32, 0.8);
|
||||
}
|
||||
|
||||
/* removing transition for every element, to avoid unnecessary movement */
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
|
||||
--allowed-color: #3c7;
|
||||
--blocked-color: var(--danger-color);
|
||||
|
||||
--overlay-bgcolor: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* removing transition for every element, to avoid unnecessary movement */
|
||||
|
||||
@ -2123,8 +2123,25 @@ td.highlight {
|
||||
}
|
||||
|
||||
/*** add style to the information/about dropdown ***/
|
||||
|
||||
.navbar-nav > .user-menu > .dropdown-menu > .user-header,
|
||||
.navbar-nav > .user-menu > .dropdown-menu > .user-footer {
|
||||
background-color: #111111;
|
||||
}
|
||||
|
||||
.save-button-container {
|
||||
border-radius: 50px;
|
||||
background: none;
|
||||
box-shadow:
|
||||
0 0 10px -1px #000,
|
||||
0 0 20px 2px #000;
|
||||
bottom: 2em;
|
||||
right: 1.7em;
|
||||
}
|
||||
.save-button {
|
||||
margin: 0;
|
||||
}
|
||||
@media (min-width: 1320px) {
|
||||
.layout-boxed .save-button-container {
|
||||
right: calc((100% - 1320px) / 2 + 0.85em);
|
||||
}
|
||||
}
|
||||
|
||||
2
style/vendor/bootstrap-select.min.css
vendored
2
style/vendor/bootstrap-select.min.css
vendored
@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* Bootstrap-select v1.13.17 (https://developer.snapappointments.com/bootstrap-select)
|
||||
* Bootstrap-select v1.13.18 (https://developer.snapappointments.com/bootstrap-select)
|
||||
*
|
||||
* Copyright 2012-2020 SnapAppointments, LLC
|
||||
* Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user