Force plugins.php action icons over POST and enlarge Tabler icons (#8394)

The install, enable, remote enable, load and dialog-confirmed actions on
plugins.php are guarded by csrf_require_post() on the server. The anchors
were still activated by the generic GET anchor handler, so clicking them
returned a 405 on the develop branch. Submit these actions over POST:

- The dialog Continue button and the Check Latest control now post.
- A capture-phase document click listener intercepts the install, enable,
  remote enable and load icons before the bubble-phase generic handler, so
  they post, keep working after Ajax table refreshes, and bind only once.

Tabler (i.ti) icons rendered smaller than the FontAwesome glyphs used on
the 1.2.x branch, so give them an explicit 16px rule matching the fa
layout in each bundled theme, and cover the new rule with
develop-theme-correctness targeted tests.
This commit is contained in:
TheWitness authored and GitHub committed 2026-10-15 13:39:18 -04:00
1 parent cf70ba4cf2
commit 5c1577a382
13 files changed
+127 -2

No files matched your search

+7
View File
@@ -144,6 +144,13 @@ a:hover {
color: #333333;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+7
View File
@@ -144,6 +144,13 @@ a:hover {
color: #333333;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+7
View File
@@ -92,6 +92,13 @@ img {
vertical-align: middle;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+7
View File
@@ -113,6 +113,13 @@ img {
vertical-align: middle;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
padding: 0px 3px;
font-size: 15px;
+7
View File
@@ -144,6 +144,13 @@ a:hover {
color: #333333;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+7
View File
@@ -97,6 +97,13 @@ img {
vertical-align: middle;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+7
View File
@@ -72,6 +72,13 @@ img {
vertical-align: middle;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+7
View File
@@ -74,6 +74,13 @@ img {
vertical-align: middle;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+7
View File
@@ -144,6 +144,13 @@ a:hover {
color: #333333;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+7
View File
@@ -87,6 +87,13 @@ img {
vertical-align: middle;
}
i.ti {
font-size: 16px;
padding: 0px 3px;
text-align: left;
vertical-align: middle;
}
i.fa, i.fas, i.fab, i.far, i.fal {
font-size: 15px;
padding: 0px 3px;
+34 -2
View File
@@ -803,7 +803,10 @@ function update_show_current() : void {
post['__csrf_magic'] = csrfMagicToken;
postUrl({url: url, noState: true}, post);
} else {
loadUrl({url: url});
// Navigate the whole page so the jQuery UI dialog is cleared; an
// Ajax postUrl() would leave #pidialog detached under body and
// break the next confirmation.
submitPageUsingPost(url);
}
}
}
@@ -872,9 +875,38 @@ function update_show_current() : void {
$('#latest').click(function() {
strURL = 'plugins.php?action=latest';
loadUrl({url:strURL});
submitPageUsingPost(strURL);
});
// The install, enable, remote enable and load actions are guarded by
// csrf_require_post() on the server, so they must be submitted over POST.
// A capture-phase listener on the document intercepts these clicks before
// the bubble-phase generic anchor handler (which would issue a GET and be
// rejected), works for rows added by later Ajax table refreshes, and is
// bound only once for the life of the page.
if (!window.cactiPluginPostActionsBound) {
window.cactiPluginPostActionsBound = true;
document.addEventListener('click', function(event) {
var anchor = event.target.closest('a.piinstall, a.pienable, a.piload');
if (anchor === null) {
return;
}
var url = anchor.getAttribute('href');
if (url === null || url === '' || url === '#') {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
postUrl({ url: url }, { __csrf_magic: csrfMagicToken });
}, true);
}
$('#form_plugins').submit(function(event) {
event.preventDefault();
applyFilter();
@@ -64,6 +64,16 @@ for (const [file, html, selector, pseudo, property, expected] of [
['include/themes/midwinter/css/media/compact-landscape.css', '<div class="navBreadCrumb"><div>Console</div><div>Devices</div></div>', '.navBreadCrumb div:first-child', '::after', 'fontFamily', /^"Font Awesome 5 Free", sans-serif$/],
['include/themes/midwinter/css/media/compact-landscape.css', '<div class="navBox-header-dropdown-content"><a href="#item">Item</a></div>', 'a', '', 'textDecorationLine', /^none$/],
['include/themes/midwinter/css/media/compact-landscape.css', '<div class="navBox-content"><div class="mdw-jstree-wrapper"><ul><li class="jstree-node jstree-closed"><i class="jstree-ocl"></i></li></ul></div></div>', '.jstree-ocl', '::before', 'fontFamily', /^tabler-icons, sans-serif$/],
['include/themes/cacti/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/carrot/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/dark/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/deepness/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/hollyberry/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/modern/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/paper-plane/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/paw/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/raspberry/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
['include/themes/sunrise/main.css', '<i class="ti"></i>', 'i.ti', '', 'fontSize', /^16px$/],
] as [string, string, string, string, string, RegExp][]) {
test(`${file} keeps repaired ${selector}${pseudo} ${property}`, async ({ page }) => {
await page.setContent(`<!DOCTYPE html>${html}`);
+13
View File
@@ -97,3 +97,16 @@ test('the state-action handler survives element-level unbind calls', () => {
assert.match(source, /\$\(document\)\.rebind\('click\.cactiPostAction', 'a\.cactiPostAction'/);
assert.doesNotMatch(source, /\$\('a\.cactiPostAction'\)\.off\('click\.cactiPostAction'/);
});
test('plugins.php confirmation Continue navigates the page so repeated confirmations keep opening', () => {
const pageSource = fs.readFileSync(path.join(__dirname, '..', '..', 'plugins.php'), 'utf8');
const dialog = extractFunction(pageSource, 'displayDialog');
// Without an inline #dialogForm the Continue button must navigate the whole
// page via submitPageUsingPost(). An Ajax loadUrl()/postUrl() here leaves the
// jQuery UI dialog detached under <body>, so the second confirmation's
// remove().append() builds into a detached node and never opens.
assert.match(dialog, /else\s*\{[^}]*submitPageUsingPost\(url\);/);
assert.doesNotMatch(dialog, /else\s*\{\s*(?:loadUrl|postUrl)\(/);
});