From c390780054400a26abc64536519a40a061784ec5 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Tue, 16 Jun 2026 17:24:54 +0200 Subject: [PATCH 01/46] feat: aggiunto badge con conteggio checkbox attive per ogni accordion in edit_host Ogni titolo di gruppo (es. "Services - DB") mostra ora il numero di checkbox spuntate tra parentesi arancioni. Il contatore si aggiorna in tempo reale al cambio delle checkbox. Aggiornato anche homepageURL dal vecchio GitLab al nuovo Forgejo. --- user_script/checkmk_swissknife.user.js | 105 +++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 13c2cb0..348d57a 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,10 +1,10 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.0 +// @version 2.1 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto -// @homepageURL https://gitlab.luigidacunto.com/consultant/checkmk-swissknife +// @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife // @updateURL https://luigidacunto.com/scripts/checkmk-swissknife/user_script/checkmk_swissknife.user.js // @downloadURL https://luigidacunto.com/scripts/checkmk-swissknife/user_script/checkmk_swissknife.user.js // @include /^https?:\/\/.+\/check_mk\/(index|wato)\.py/ @@ -406,16 +406,88 @@ } + // ========================================================================= + // FEATURE: Accordion Checked Count Badge + // + // Mostra nel titolo di ogni accordion della pagina edit_host il numero + // di checkbox attive nel gruppo. Es: "Services - DB (1)". + // Il contatore si aggiorna in tempo reale al cambio delle checkbox. + // ========================================================================= + + function updateAccordionBadge(td) { + const table = td.closest('table.nform'); + if (!table) return; + const tbody = table.querySelector('tbody'); + if (!tbody) return; + const checked = tbody.querySelectorAll('input[type=checkbox]:checked').length; + const badge = td.querySelector('.cmk-sk-acc-count'); + if (!badge) return; + if (checked > 0) { + badge.textContent = `(${checked})`; + badge.style.display = 'inline'; + } else { + badge.style.display = 'none'; + } + } + + function initAccordionCheckedCounts(iDoc) { + const form = iDoc.getElementById('form_edit_host'); + if (!form) return false; + if (form.dataset.cmkAccBadge === '1') return true; + + injectStyles(iDoc, 'cmk-sk-acc-badge-styles', ` + .cmk-sk-acc-count { + margin-left: 6px; + padding: 1px 6px; + background: #f0a500; + color: #000; + border-radius: 9px; + font-size: 11px; + font-weight: bold; + vertical-align: middle; + } + `); + + iDoc.querySelectorAll('table.nform thead tr.heading td').forEach(td => { + const table = td.closest('table.nform'); + const tbody = table?.querySelector('tbody'); + if (!tbody) return; + + const badge = iDoc.createElement('span'); + badge.className = 'cmk-sk-acc-count'; + badge.style.display = 'none'; + + const img = td.querySelector('img.treeangle'); + const afterImg = img?.nextSibling; + if (afterImg) { + afterImg.after(badge); + } else { + td.appendChild(badge); + } + + updateAccordionBadge(td); + + tbody.addEventListener('change', (e) => { + if (e.target.type === 'checkbox') updateAccordionBadge(td); + }); + }); + + form.dataset.cmkAccBadge = '1'; + return true; + } + + // ========================================================================= // BOOTSTRAP: polling per ogni feature, attivato solo se la select è presente // ========================================================================= - let attempts = 0; + let attemptsFolder = 0; + let attemptsAcc = 0; function tryEnhanceFolderSelect() { const iDoc = getWatoDoc(FOLDER_SELECT_ID); if (!iDoc || !iDoc.body) { - if (++attempts < MAX_ATTEMPTS) setTimeout(tryEnhanceFolderSelect, POLL_INTERVAL_MS); + if (++attemptsFolder < MAX_ATTEMPTS) setTimeout(tryEnhanceFolderSelect, POLL_INTERVAL_MS); return; } @@ -426,16 +498,30 @@ } if (!sel.classList.contains('select2-hidden-accessible')) { - if (++attempts < MAX_ATTEMPTS) setTimeout(tryEnhanceFolderSelect, POLL_INTERVAL_MS); + if (++attemptsFolder < MAX_ATTEMPTS) setTimeout(tryEnhanceFolderSelect, POLL_INTERVAL_MS); return; } buildCustomSearchOverlay(iDoc, sel); } + function tryInitAccordionCounts() { + const iDoc = getWatoDoc('form_edit_host'); + if (!iDoc || !iDoc.body) { + if (++attemptsAcc < MAX_ATTEMPTS) setTimeout(tryInitAccordionCounts, POLL_INTERVAL_MS); + return; + } + if (!iDoc.getElementById('form_edit_host')) return; + if (!initAccordionCheckedCounts(iDoc)) { + if (++attemptsAcc < MAX_ATTEMPTS) setTimeout(tryInitAccordionCounts, POLL_INTERVAL_MS); + } + } + function init() { - attempts = 0; + attemptsFolder = 0; + attemptsAcc = 0; setTimeout(tryEnhanceFolderSelect, 800); + setTimeout(tryInitAccordionCounts, 800); } if (document.readyState === 'complete') { @@ -450,9 +536,14 @@ if (!iDoc) return; const sel = iDoc.getElementById(FOLDER_SELECT_ID); if (sel && sel.classList.contains('select2-hidden-accessible') && !sel.dataset.cmkEnhanced) { - attempts = 0; + attemptsFolder = 0; setTimeout(tryEnhanceFolderSelect, 300); } + const form = iDoc.getElementById('form_edit_host'); + if (form && !form.dataset.cmkAccBadge) { + attemptsAcc = 0; + setTimeout(tryInitAccordionCounts, 300); + } }).observe(document.body, { childList: true, subtree: true }); // Riavvia al caricamento dell'iframe (layout con sidebar) From 1a9db50817c493b6b67334bb09c60a8bcb5343e2 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Tue, 16 Jun 2026 17:36:57 +0200 Subject: [PATCH 02/46] feat: aggiunto badge blu con conteggio valori ereditati da folder parent negli accordion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accanto al badge arancione (checkbox spuntate), compare ora un badge blu "↑N" che conta le proprietà con valore esplicitamente ereditato da una cartella padre ("Inherited from X"), escludendo i semplici "(Default value)". Il contatore si aggiorna in tempo reale al cambio delle checkbox. --- user_script/checkmk_swissknife.user.js | 44 ++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 348d57a..37d906d 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.1 +// @version 2.2 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -430,11 +430,31 @@ } } + function updateInheritedBadge(td, iWin) { + const table = td.closest('table.nform'); + if (!table) return; + const tbody = table.querySelector('tbody'); + if (!tbody) return; + const count = Array.from(tbody.querySelectorAll('div.inherited')).filter(el => + iWin.getComputedStyle(el).display !== 'none' && el.textContent.includes('Inherited from') + ).length; + const badge = td.querySelector('.cmk-sk-inh-count'); + if (!badge) return; + if (count > 0) { + badge.textContent = `↑${count}`; + badge.style.display = 'inline'; + } else { + badge.style.display = 'none'; + } + } + function initAccordionCheckedCounts(iDoc) { const form = iDoc.getElementById('form_edit_host'); if (!form) return false; if (form.dataset.cmkAccBadge === '1') return true; + const iWin = iDoc.defaultView; + injectStyles(iDoc, 'cmk-sk-acc-badge-styles', ` .cmk-sk-acc-count { margin-left: 6px; @@ -446,6 +466,16 @@ font-weight: bold; vertical-align: middle; } + .cmk-sk-inh-count { + margin-left: 4px; + padding: 1px 6px; + background: #5ba4e5; + color: #000; + border-radius: 9px; + font-size: 11px; + font-weight: bold; + vertical-align: middle; + } `); iDoc.querySelectorAll('table.nform thead tr.heading td').forEach(td => { @@ -457,18 +487,28 @@ badge.className = 'cmk-sk-acc-count'; badge.style.display = 'none'; + const badgeInh = iDoc.createElement('span'); + badgeInh.className = 'cmk-sk-inh-count'; + badgeInh.style.display = 'none'; + const img = td.querySelector('img.treeangle'); const afterImg = img?.nextSibling; if (afterImg) { afterImg.after(badge); + badge.after(badgeInh); } else { td.appendChild(badge); + td.appendChild(badgeInh); } updateAccordionBadge(td); + updateInheritedBadge(td, iWin); tbody.addEventListener('change', (e) => { - if (e.target.type === 'checkbox') updateAccordionBadge(td); + if (e.target.type === 'checkbox') { + updateAccordionBadge(td); + updateInheritedBadge(td, iWin); + } }); }); From a0a0f20df333e44d4e8680cc663c9e1953f807f3 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Tue, 16 Jun 2026 17:43:33 +0200 Subject: [PATCH 03/46] fix: guard URL-based per attivare le feature solo sulle pagine target Aggiunto helper getPageMode(iDoc) che legge il parametro "mode" dall'URL senza toccare il DOM. Accordion badges ora si attiva solo su mode=edit_host (guard in tryInitAccordionCounts, init e MutationObserver), evitando operazioni inutili e potenziali errori in console sulle altre pagine. --- user_script/checkmk_swissknife.user.js | 31 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 37d906d..979cffb 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.2 +// @version 2.3 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -37,6 +37,12 @@ return null; } + // Legge il parametro "mode" dall'URL del documento target senza accedere al DOM + function getPageMode(iDoc) { + try { return new URLSearchParams(iDoc.location.search).get('mode') || ''; } + catch (e) { return ''; } + } + // Inietta CSS nel documento target (una sola volta, deduplica per id) function injectStyles(iDoc, id, css) { if (iDoc.getElementById(id)) return; @@ -551,17 +557,25 @@ if (++attemptsAcc < MAX_ATTEMPTS) setTimeout(tryInitAccordionCounts, POLL_INTERVAL_MS); return; } - if (!iDoc.getElementById('form_edit_host')) return; + // Guard URL: attiva solo sulla pagina edit_host + if (getPageMode(iDoc) !== 'edit_host') return; if (!initAccordionCheckedCounts(iDoc)) { if (++attemptsAcc < MAX_ATTEMPTS) setTimeout(tryInitAccordionCounts, POLL_INTERVAL_MS); } } function init() { + const iDoc = getWatoDoc(FOLDER_SELECT_ID); + const mode = getPageMode(iDoc); attemptsFolder = 0; attemptsAcc = 0; + // Folder select: si auto-ferma se non trova l'elemento, schedula sempre. setTimeout(tryEnhanceFolderSelect, 800); - setTimeout(tryInitAccordionCounts, 800); + // Accordion: solo su edit_host. Se mode è vuoto (iframe non ancora caricato) + // si schedula comunque: tryInitAccordionCounts farà il guard URL. + if (!mode || mode === 'edit_host') { + setTimeout(tryInitAccordionCounts, 800); + } } if (document.readyState === 'complete') { @@ -574,15 +588,18 @@ new MutationObserver(() => { const iDoc = getWatoDoc(FOLDER_SELECT_ID); if (!iDoc) return; + const mode = getPageMode(iDoc); const sel = iDoc.getElementById(FOLDER_SELECT_ID); if (sel && sel.classList.contains('select2-hidden-accessible') && !sel.dataset.cmkEnhanced) { attemptsFolder = 0; setTimeout(tryEnhanceFolderSelect, 300); } - const form = iDoc.getElementById('form_edit_host'); - if (form && !form.dataset.cmkAccBadge) { - attemptsAcc = 0; - setTimeout(tryInitAccordionCounts, 300); + if (mode === 'edit_host') { + const form = iDoc.getElementById('form_edit_host'); + if (form && !form.dataset.cmkAccBadge) { + attemptsAcc = 0; + setTimeout(tryInitAccordionCounts, 300); + } } }).observe(document.body, { childList: true, subtree: true }); From 42ca74e8cc52a9e067079f1e3043a40b84e587a0 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Tue, 16 Jun 2026 17:51:35 +0200 Subject: [PATCH 04/46] feat: estesi accordion badge anche alla pagina bulkedit Aggiunta costante ACCORDION_MODES (Set con 'edit_host' e 'bulkedit') per centralizzare il guard URL. I badge di conteggio checkbox e valori ereditati ora si attivano su entrambe le pagine di modifica host, singola e massiva. --- user_script/checkmk_swissknife.user.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 979cffb..86d2a00 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.3 +// @version 2.4 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -43,6 +43,9 @@ catch (e) { return ''; } } + // Pagine che supportano gli accordion badge (stessa struttura form_edit_host + table.nform) + const ACCORDION_MODES = new Set(['edit_host', 'bulkedit']); + // Inietta CSS nel documento target (una sola volta, deduplica per id) function injectStyles(iDoc, id, css) { if (iDoc.getElementById(id)) return; @@ -557,8 +560,8 @@ if (++attemptsAcc < MAX_ATTEMPTS) setTimeout(tryInitAccordionCounts, POLL_INTERVAL_MS); return; } - // Guard URL: attiva solo sulla pagina edit_host - if (getPageMode(iDoc) !== 'edit_host') return; + // Guard URL: attiva solo sulle pagine con accordion supportati + if (!ACCORDION_MODES.has(getPageMode(iDoc))) return; if (!initAccordionCheckedCounts(iDoc)) { if (++attemptsAcc < MAX_ATTEMPTS) setTimeout(tryInitAccordionCounts, POLL_INTERVAL_MS); } @@ -571,9 +574,9 @@ attemptsAcc = 0; // Folder select: si auto-ferma se non trova l'elemento, schedula sempre. setTimeout(tryEnhanceFolderSelect, 800); - // Accordion: solo su edit_host. Se mode è vuoto (iframe non ancora caricato) - // si schedula comunque: tryInitAccordionCounts farà il guard URL. - if (!mode || mode === 'edit_host') { + // Accordion: solo sulle pagine in ACCORDION_MODES. Se mode è vuoto (iframe non ancora + // caricato) si schedula comunque: tryInitAccordionCounts farà il guard URL. + if (!mode || ACCORDION_MODES.has(mode)) { setTimeout(tryInitAccordionCounts, 800); } } @@ -594,7 +597,7 @@ attemptsFolder = 0; setTimeout(tryEnhanceFolderSelect, 300); } - if (mode === 'edit_host') { + if (ACCORDION_MODES.has(mode)) { const form = iDoc.getElementById('form_edit_host'); if (form && !form.dataset.cmkAccBadge) { attemptsAcc = 0; From 01a74caef5011578b5cbafbbb877363340798e23 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Tue, 16 Jun 2026 17:56:47 +0200 Subject: [PATCH 05/46] feat: aggiunto badge rosso con conteggio valori in conflitto tra host (bulkedit) Terzo badge con simbolo neq N (rosso) che conta le proprieta con This value differs between the selected hosts. Appare solo sulla pagina bulkedit dove ha senso. Aggiunto updateDiffBadge() e CSS .cmk-sk-diff-count. --- user_script/checkmk_swissknife.user.js | 40 +++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 86d2a00..bdba804 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.4 +// @version 2.5 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -457,12 +457,31 @@ } } + function updateDiffBadge(td, iWin) { + const table = td.closest('table.nform'); + if (!table) return; + const tbody = table.querySelector('tbody'); + if (!tbody) return; + const count = Array.from(tbody.querySelectorAll('div.inherited')).filter(el => + iWin.getComputedStyle(el).display !== 'none' && el.textContent.includes('This value differs') + ).length; + const badge = td.querySelector('.cmk-sk-diff-count'); + if (!badge) return; + if (count > 0) { + badge.textContent = `≠${count}`; + badge.style.display = 'inline'; + } else { + badge.style.display = 'none'; + } + } + function initAccordionCheckedCounts(iDoc) { const form = iDoc.getElementById('form_edit_host'); if (!form) return false; if (form.dataset.cmkAccBadge === '1') return true; const iWin = iDoc.defaultView; + const isBulkEdit = getPageMode(iDoc) === 'bulkedit'; injectStyles(iDoc, 'cmk-sk-acc-badge-styles', ` .cmk-sk-acc-count { @@ -485,6 +504,16 @@ font-weight: bold; vertical-align: middle; } + .cmk-sk-diff-count { + margin-left: 4px; + padding: 1px 6px; + background: #e55b5b; + color: #fff; + border-radius: 9px; + font-size: 11px; + font-weight: bold; + vertical-align: middle; + } `); iDoc.querySelectorAll('table.nform thead tr.heading td').forEach(td => { @@ -510,13 +539,22 @@ td.appendChild(badgeInh); } + if (isBulkEdit) { + const badgeDiff = iDoc.createElement('span'); + badgeDiff.className = 'cmk-sk-diff-count'; + badgeDiff.style.display = 'none'; + badgeInh.after(badgeDiff); + } + updateAccordionBadge(td); updateInheritedBadge(td, iWin); + if (isBulkEdit) updateDiffBadge(td, iWin); tbody.addEventListener('change', (e) => { if (e.target.type === 'checkbox') { updateAccordionBadge(td); updateInheritedBadge(td, iWin); + if (isBulkEdit) updateDiffBadge(td, iWin); } }); }); From df39279c41203a10eb1e63d6dba76b510e5e95c3 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 15:01:00 +0200 Subject: [PATCH 06/46] feat: highlight visivo regole ineffective in pagine edit_ruleset Aggiunge badge arancione 'ineffective' e bordo sinistro alle righe marcate con icon_hyphen.svg (title='Ineffective rule'). Funziona sia su wato.py diretto che dentro index.py con sidebar. Aggiunto helper getTargetDoc() per gestire entrambi i casi. Attivazione via guard URL mode=edit_ruleset. --- user_script/checkmk_swissknife.user.js | 97 ++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index bdba804..537cc71 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.5 +// @version 2.6 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -43,6 +43,14 @@ catch (e) { return ''; } } + // Restituisce il documento su cui operare, gestendo sia il caso iframe (index.py con + // sidebar) che il caso direct (wato.py aperto senza sidebar, nessun iframe presente). + function getTargetDoc() { + const iframe = document.querySelector('iframe[name="main"], iframe#main'); + if (iframe) { try { return iframe.contentDocument; } catch (e) { return null; } } + return document; + } + // Pagine che supportano gli accordion badge (stessa struttura form_edit_host + table.nform) const ACCORDION_MODES = new Set(['edit_host', 'bulkedit']); @@ -564,12 +572,67 @@ } + // ========================================================================= + // FEATURE: Ineffective Rule Highlight + // + // Nelle pagine mode=edit_ruleset sostituisce l'icona poco visibile + // "icon_hyphen.svg" (title="Ineffective rule") con un badge colorato + // e aggiunge un bordo sinistro alla riga. + // Funziona sia su wato.py diretto (no iframe) che dentro index.py (iframe). + // ========================================================================= + + function highlightIneffectiveRules(doc) { + if (doc.body.dataset.cmkIneffHighlight === '1') return; + + const imgs = doc.querySelectorAll('img.icon[title="Ineffective rule"]'); + if (!imgs.length) return; + + injectStyles(doc, 'cmk-sk-ineff-styles', ` + tr.cmk-sk-ineffective > td:first-child { + border-left: 4px solid #e5a500 !important; + } + tr.cmk-sk-ineffective { + background: rgba(229, 165, 0, 0.08) !important; + } + .cmk-sk-ineff-badge { + display: inline-block; + background: #e5a500; + color: #000; + font-size: 10px; + font-weight: bold; + padding: 2px 6px; + border-radius: 3px; + white-space: nowrap; + font-family: monospace; + vertical-align: middle; + cursor: default; + letter-spacing: 0.03em; + } + `); + + imgs.forEach(img => { + const row = img.closest('tr'); + if (!row || row.classList.contains('cmk-sk-ineffective')) return; + row.classList.add('cmk-sk-ineffective'); + + const badge = doc.createElement('span'); + badge.className = 'cmk-sk-ineff-badge'; + badge.title = 'Ineffective rule'; + badge.textContent = '⚠ ineffective'; + img.replaceWith(badge); + }); + + doc.body.dataset.cmkIneffHighlight = '1'; + } + + // ========================================================================= // BOOTSTRAP: polling per ogni feature, attivato solo se la select è presente // ========================================================================= - let attemptsFolder = 0; - let attemptsAcc = 0; + let attemptsFolder = 0; + let attemptsAcc = 0; + let attemptsRuleset = 0; function tryEnhanceFolderSelect() { const iDoc = getWatoDoc(FOLDER_SELECT_ID); @@ -605,18 +668,34 @@ } } + function tryHighlightIneffective() { + const doc = getTargetDoc(); + if (!doc || !doc.body) { + if (++attemptsRuleset < MAX_ATTEMPTS) setTimeout(tryHighlightIneffective, POLL_INTERVAL_MS); + return; + } + if (getPageMode(doc) !== 'edit_ruleset') return; + highlightIneffectiveRules(doc); + } + function init() { const iDoc = getWatoDoc(FOLDER_SELECT_ID); const mode = getPageMode(iDoc); - attemptsFolder = 0; - attemptsAcc = 0; + const targetDoc = getTargetDoc(); + const targetMode = getPageMode(targetDoc); + attemptsFolder = 0; + attemptsAcc = 0; + attemptsRuleset = 0; // Folder select: si auto-ferma se non trova l'elemento, schedula sempre. setTimeout(tryEnhanceFolderSelect, 800); - // Accordion: solo sulle pagine in ACCORDION_MODES. Se mode è vuoto (iframe non ancora - // caricato) si schedula comunque: tryInitAccordionCounts farà il guard URL. + // Accordion: solo sulle pagine in ACCORDION_MODES. if (!mode || ACCORDION_MODES.has(mode)) { setTimeout(tryInitAccordionCounts, 800); } + // Ineffective rule highlight: solo su edit_ruleset. + if (!targetMode || targetMode === 'edit_ruleset') { + setTimeout(tryHighlightIneffective, 300); + } } if (document.readyState === 'complete') { @@ -642,6 +721,10 @@ setTimeout(tryInitAccordionCounts, 300); } } + if (mode === 'edit_ruleset' && iDoc.body && !iDoc.body.dataset.cmkIneffHighlight) { + attemptsRuleset = 0; + setTimeout(tryHighlightIneffective, 300); + } }).observe(document.body, { childList: true, subtree: true }); // Riavvia al caricamento dell'iframe (layout con sidebar) From 18a1fa0e497e73cd25739869b5fedf4171d2d0d0 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 15:01:40 +0200 Subject: [PATCH 07/46] chore: migrazione versioning a SemVer major.minor.patch (2.6.0) --- user_script/checkmk_swissknife.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 537cc71..cecd5e8 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.6 +// @version 2.6.0 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife From fa9ea660834ff11fa7e1a864133f1573cdc2605f Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 15:11:22 +0200 Subject: [PATCH 08/46] feat: highlight match/no-match nelle pagine edit_ruleset con contesto host Aggiunge badge verde (checkmark) alle regole che matchano e badge grigio dimmed alle regole che non matchano, sostituendo le icone poco visibili. La funzione tryHighlightRuleset ora gestisce entrambe le feature ruleset (ineffective + match status) sulle pagine mode=edit_ruleset. --- user_script/checkmk_swissknife.user.js | 101 +++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 7 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index cecd5e8..3fa9857 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.6.0 +// @version 2.7.0 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -626,6 +626,91 @@ } + // ========================================================================= + // FEATURE: Rule Match Status Highlight + // + // Nelle pagine mode=edit_ruleset aperte con contesto host/service (parametri + // host= e service= nell'URL), evidenzia le righe che matchano (verde) e + // attenua quelle che non matchano (grigio), sostituendo le icone poco + // visibili icon_checkmark e icon_hyphen con badge colorati. + // ========================================================================= + + function highlightRuleMatchStatus(doc) { + if (doc.body.dataset.cmkMatchHighlight === '1') return; + + const matchImgs = doc.querySelectorAll('img.icon[title^="This rule matches"]'); + const noMatchImgs = doc.querySelectorAll('img.icon[title^="This rule does not match"]'); + if (!matchImgs.length && !noMatchImgs.length) return; + + injectStyles(doc, 'cmk-sk-match-styles', ` + tr.cmk-sk-rule-match > td:first-child { + border-left: 4px solid #4caf50 !important; + } + tr.cmk-sk-rule-match { + background: rgba(76, 175, 80, 0.10) !important; + } + tr.cmk-sk-rule-nomatch { + opacity: 0.45; + } + tr.cmk-sk-rule-nomatch > td:first-child { + border-left: 4px solid #555 !important; + } + .cmk-sk-match-badge { + display: inline-block; + background: #4caf50; + color: #fff; + font-size: 10px; + font-weight: bold; + padding: 2px 6px; + border-radius: 3px; + white-space: nowrap; + font-family: monospace; + vertical-align: middle; + cursor: default; + letter-spacing: 0.03em; + } + .cmk-sk-nomatch-badge { + display: inline-block; + background: #444; + color: #888; + font-size: 10px; + font-weight: bold; + padding: 2px 6px; + border-radius: 3px; + white-space: nowrap; + font-family: monospace; + vertical-align: middle; + cursor: default; + letter-spacing: 0.03em; + } + `); + + matchImgs.forEach(img => { + const row = img.closest('tr'); + if (!row) return; + row.classList.add('cmk-sk-rule-match'); + const badge = doc.createElement('span'); + badge.className = 'cmk-sk-match-badge'; + badge.title = img.title; + badge.textContent = '✓ match'; + img.replaceWith(badge); + }); + + noMatchImgs.forEach(img => { + const row = img.closest('tr'); + if (!row) return; + row.classList.add('cmk-sk-rule-nomatch'); + const badge = doc.createElement('span'); + badge.className = 'cmk-sk-nomatch-badge'; + badge.title = img.title; + badge.textContent = '✗ no match'; + img.replaceWith(badge); + }); + + doc.body.dataset.cmkMatchHighlight = '1'; + } + + // ========================================================================= // BOOTSTRAP: polling per ogni feature, attivato solo se la select è presente // ========================================================================= @@ -668,14 +753,15 @@ } } - function tryHighlightIneffective() { + function tryHighlightRuleset() { const doc = getTargetDoc(); if (!doc || !doc.body) { - if (++attemptsRuleset < MAX_ATTEMPTS) setTimeout(tryHighlightIneffective, POLL_INTERVAL_MS); + if (++attemptsRuleset < MAX_ATTEMPTS) setTimeout(tryHighlightRuleset, POLL_INTERVAL_MS); return; } if (getPageMode(doc) !== 'edit_ruleset') return; highlightIneffectiveRules(doc); + highlightRuleMatchStatus(doc); } function init() { @@ -692,9 +778,9 @@ if (!mode || ACCORDION_MODES.has(mode)) { setTimeout(tryInitAccordionCounts, 800); } - // Ineffective rule highlight: solo su edit_ruleset. + // Ruleset enhancements (ineffective + match status): solo su edit_ruleset. if (!targetMode || targetMode === 'edit_ruleset') { - setTimeout(tryHighlightIneffective, 300); + setTimeout(tryHighlightRuleset, 300); } } @@ -721,9 +807,10 @@ setTimeout(tryInitAccordionCounts, 300); } } - if (mode === 'edit_ruleset' && iDoc.body && !iDoc.body.dataset.cmkIneffHighlight) { + if (mode === 'edit_ruleset' && iDoc.body && + (!iDoc.body.dataset.cmkIneffHighlight || !iDoc.body.dataset.cmkMatchHighlight)) { attemptsRuleset = 0; - setTimeout(tryHighlightIneffective, 300); + setTimeout(tryHighlightRuleset, 300); } }).observe(document.body, { childList: true, subtree: true }); From d4d994e7ccaa9ecab20452755080b16a39f3aed2 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 15:17:31 +0200 Subject: [PATCH 09/46] feat: estendi badge match alle ricerche per tag su edit_ruleset Le pagine edit_ruleset aperte da una ricerca per hosttag usano title="Matches" invece di title^="This rule matches". Esteso il selettore CSS per coprire entrambi i casi. bump version to 2.7.1 --- user_script/checkmk_swissknife.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 3fa9857..4377823 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.7.0 +// @version 2.7.1 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -638,7 +638,7 @@ function highlightRuleMatchStatus(doc) { if (doc.body.dataset.cmkMatchHighlight === '1') return; - const matchImgs = doc.querySelectorAll('img.icon[title^="This rule matches"]'); + const matchImgs = doc.querySelectorAll('img.icon[title^="This rule matches"], img.icon[title="Matches"]'); const noMatchImgs = doc.querySelectorAll('img.icon[title^="This rule does not match"]'); if (!matchImgs.length && !noMatchImgs.length) return; From d00483cc2fb4ab0183fd0af026fdc5a6512683cd Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 15:38:33 +0200 Subject: [PATCH 10/46] feat: aggiungi toggle filtra/mostra righe irrilevanti su edit_ruleset Aggiunge una barra in cima alle pagine edit_ruleset con un pulsante "Solo rilevanti" / "Mostra tutto" che nasconde le righe e i folder senza regole rilevanti (match, no-match, ineffective). Utile quando su centinaia di regole solo poche sono evidenziate dalla ricerca. bump version to 2.8.0 --- user_script/checkmk_swissknife.user.js | 93 +++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 4377823..1906b08 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.7.1 +// @version 2.8.0 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -711,6 +711,96 @@ } + // ========================================================================= + // FEATURE: Ruleset Filter Toggle + // + // Dopo che le funzioni di highlight hanno marcato le righe rilevanti + // (match, no-match, ineffective), aggiunge una barra in cima con un pulsante + // per nascondere le righe e i folder senza alcuna rilevanza. Utile su + // pagine con centinaia di regole dove quelle rilevanti sono poche. + // ========================================================================= + + function addRulesetFilterToggle(doc) { + if (doc.body.dataset.cmkFilterToggle === '1') return; + doc.body.dataset.cmkFilterToggle = '1'; + + const RELEVANT_SEL = 'tr.cmk-sk-rule-match, tr.cmk-sk-rule-nomatch, tr.cmk-sk-ineffective'; + + doc.querySelectorAll('tr.data').forEach(row => { + if (!row.matches(RELEVANT_SEL)) row.classList.add('cmk-sk-irrelevant-row'); + }); + + doc.querySelectorAll('div.foldable_wrapper').forEach(wrapper => { + if (!wrapper.querySelector(RELEVANT_SEL)) wrapper.classList.add('cmk-sk-irrelevant-folder'); + }); + + const relevantCount = doc.querySelectorAll(RELEVANT_SEL).length; + const irrelevantRows = doc.querySelectorAll('tr.cmk-sk-irrelevant-row').length; + const irrelevantFolders = doc.querySelectorAll('div.foldable_wrapper.cmk-sk-irrelevant-folder').length; + + if (!irrelevantRows && !irrelevantFolders) return; + + injectStyles(doc, 'cmk-sk-filter-toggle-styles', ` + #cmk-sk-filter-bar { + display: flex; + align-items: center; + gap: 10px; + padding: 5px 10px; + margin: 6px 0 4px 0; + background: rgba(0,0,0,0.25); + border: 1px solid #3a3a3a; + border-radius: 4px; + font-size: 11px; + color: #999; + font-family: monospace; + } + #cmk-sk-filter-toggle-btn { + cursor: pointer; + padding: 3px 10px; + border-radius: 3px; + border: 1px solid #555; + background: #2a2a2a; + color: #bbb; + font-size: 11px; + font-family: monospace; + font-weight: bold; + letter-spacing: 0.03em; + } + #cmk-sk-filter-toggle-btn:hover { background: #383838; } + #cmk-sk-filter-toggle-btn.active { + background: #1c3320; + border-color: #4caf50; + color: #4caf50; + } + body.cmk-sk-filter-active tr.cmk-sk-irrelevant-row { display: none !important; } + body.cmk-sk-filter-active div.foldable_wrapper.cmk-sk-irrelevant-folder { display: none !important; } + `); + + const bar = doc.createElement('div'); + bar.id = 'cmk-sk-filter-bar'; + + const btn = doc.createElement('button'); + btn.id = 'cmk-sk-filter-toggle-btn'; + btn.type = 'button'; + btn.textContent = 'Solo rilevanti'; + + const info = doc.createElement('span'); + info.textContent = `${relevantCount} rilevanti · ${irrelevantRows} righe e ${irrelevantFolders} folder non rilevanti`; + + btn.addEventListener('click', () => { + const isActive = doc.body.classList.toggle('cmk-sk-filter-active'); + btn.classList.toggle('active', isActive); + btn.textContent = isActive ? 'Mostra tutto' : 'Solo rilevanti'; + }); + + bar.appendChild(btn); + bar.appendChild(info); + + const anchor = doc.querySelector('div.foldable_wrapper') || doc.querySelector('div.wato'); + if (anchor) anchor.before(bar); + } + + // ========================================================================= // BOOTSTRAP: polling per ogni feature, attivato solo se la select è presente // ========================================================================= @@ -762,6 +852,7 @@ if (getPageMode(doc) !== 'edit_ruleset') return; highlightIneffectiveRules(doc); highlightRuleMatchStatus(doc); + addRulesetFilterToggle(doc); } function init() { From e2f0de0451a5958a2ecc2d97cb2d6cc8ce3d2896 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 17:12:11 +0200 Subject: [PATCH 11/46] fix: nascondi toggle su pagine edit_ruleset senza ricerca attiva MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Il toggle "Solo rilevanti" non deve apparire su viste normali del ruleset dove nessuna riga è evidenziata (nessuna ricerca per tag, host o ineffective). Controllo anticipato su relevantCount === 0 prima di marcare righe e folder. bump version to 2.8.1 --- user_script/checkmk_swissknife.user.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 1906b08..c41835d 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.8.0 +// @version 2.8.1 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -726,6 +726,10 @@ const RELEVANT_SEL = 'tr.cmk-sk-rule-match, tr.cmk-sk-rule-nomatch, tr.cmk-sk-ineffective'; + // Nessuna riga evidenziata = nessuna ricerca attiva, toggle inutile + const relevantCount = doc.querySelectorAll(RELEVANT_SEL).length; + if (!relevantCount) return; + doc.querySelectorAll('tr.data').forEach(row => { if (!row.matches(RELEVANT_SEL)) row.classList.add('cmk-sk-irrelevant-row'); }); @@ -734,7 +738,6 @@ if (!wrapper.querySelector(RELEVANT_SEL)) wrapper.classList.add('cmk-sk-irrelevant-folder'); }); - const relevantCount = doc.querySelectorAll(RELEVANT_SEL).length; const irrelevantRows = doc.querySelectorAll('tr.cmk-sk-irrelevant-row').length; const irrelevantFolders = doc.querySelectorAll('div.foldable_wrapper.cmk-sk-irrelevant-folder').length; From 5c7046f8edafd13ebdeb83aaa4a8f74cf1f0c550 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 17:29:06 +0200 Subject: [PATCH 12/46] feat: aggiungi pulsante disco su view.py per aprire Service Discovery host Nelle monitoring views (view.py), aggiunge un badge 'disco' accanto a ogni hostname nella colonna Host. Il click apre in nuova tab la pagina wato.py?host=HOSTNAME&mode=inventory per il Service Discovery diretto. Esteso @include a coprire anche view.py. bump version to 2.9.0 --- user_script/checkmk_swissknife.user.js | 85 +++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index c41835d..24262ae 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,13 +1,13 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.8.1 +// @version 2.9.0 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife // @updateURL https://luigidacunto.com/scripts/checkmk-swissknife/user_script/checkmk_swissknife.user.js // @downloadURL https://luigidacunto.com/scripts/checkmk-swissknife/user_script/checkmk_swissknife.user.js -// @include /^https?:\/\/.+\/check_mk\/(index|wato)\.py/ +// @include /^https?:\/\/.+\/check_mk\/(index|wato|view)\.py/ // @grant none // ==/UserScript== @@ -804,13 +804,69 @@ } + // ========================================================================= + // FEATURE: Inventory Button su view.py + // + // Nelle pagine view.py (monitoring views), aggiunge un piccolo pulsante + // accanto a ogni hostname nella colonna Host per aprire direttamente la + // pagina di Service Discovery dell'host in una nuova tab. + // ========================================================================= + + function addInventoryButtons(doc) { + if (doc.body.dataset.cmkInventoryBtns === '1') return; + + const hostCells = doc.querySelectorAll('table.data td.nobr'); + if (!hostCells.length) return; + + injectStyles(doc, 'cmk-sk-inv-btn-styles', ` + .cmk-sk-inv-btn { + display: inline-block; + background: #2c6fad; + color: #fff !important; + font-size: 9px; + font-weight: bold; + padding: 1px 5px; + border-radius: 3px; + text-decoration: none !important; + margin-left: 5px; + vertical-align: middle; + font-family: monospace; + white-space: nowrap; + cursor: pointer; + opacity: 0.85; + } + .cmk-sk-inv-btn:hover { opacity: 1; background: #1a5a99 !important; } + `); + + hostCells.forEach(td => { + const link = td.querySelector('a[href*="view_name=hoststatus"]'); + if (!link) return; + const params = new URLSearchParams(link.getAttribute('href').split('?')[1] || ''); + const hostname = params.get('host'); + if (!hostname) return; + + const btn = doc.createElement('a'); + btn.className = 'cmk-sk-inv-btn'; + btn.href = `wato.py?host=${encodeURIComponent(hostname)}&mode=inventory`; + btn.target = '_blank'; + btn.rel = 'noopener'; + btn.title = `Service Discovery: ${hostname}`; + btn.textContent = 'disco'; + td.appendChild(btn); + }); + + doc.body.dataset.cmkInventoryBtns = '1'; + } + + // ========================================================================= // BOOTSTRAP: polling per ogni feature, attivato solo se la select è presente // ========================================================================= - let attemptsFolder = 0; - let attemptsAcc = 0; - let attemptsRuleset = 0; + let attemptsFolder = 0; + let attemptsAcc = 0; + let attemptsRuleset = 0; + let attemptsInventory = 0; function tryEnhanceFolderSelect() { const iDoc = getWatoDoc(FOLDER_SELECT_ID); @@ -858,14 +914,25 @@ addRulesetFilterToggle(doc); } + function tryAddInventoryButtons() { + const doc = getTargetDoc(); + if (!doc || !doc.body) { + if (++attemptsInventory < MAX_ATTEMPTS) setTimeout(tryAddInventoryButtons, POLL_INTERVAL_MS); + return; + } + try { if (!/\/view\.py/.test(doc.location.pathname)) return; } catch (e) { return; } + addInventoryButtons(doc); + } + function init() { const iDoc = getWatoDoc(FOLDER_SELECT_ID); const mode = getPageMode(iDoc); const targetDoc = getTargetDoc(); const targetMode = getPageMode(targetDoc); - attemptsFolder = 0; - attemptsAcc = 0; - attemptsRuleset = 0; + attemptsFolder = 0; + attemptsAcc = 0; + attemptsRuleset = 0; + attemptsInventory = 0; // Folder select: si auto-ferma se non trova l'elemento, schedula sempre. setTimeout(tryEnhanceFolderSelect, 800); // Accordion: solo sulle pagine in ACCORDION_MODES. @@ -876,6 +943,8 @@ if (!targetMode || targetMode === 'edit_ruleset') { setTimeout(tryHighlightRuleset, 300); } + // Inventory button: su view.py, si auto-ferma se non applicabile. + setTimeout(tryAddInventoryButtons, 500); } if (document.readyState === 'complete') { From 9b9d39613c15ec081faf5643c861357f2984c9a5 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 17:32:45 +0200 Subject: [PATCH 13/46] fix: sostituisci testo 'disco' con icona SVG sul pulsante inventory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Il pulsante Service Discovery usa ora un'icona lente SVG inline con bordino blu al posto del testo. Dimensione 16x16px, trasparente, con hover che aumenta l'opacità e schiarisce il bordo. bump version to 2.9.1 --- user_script/checkmk_swissknife.user.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 24262ae..aee1d76 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.9.0 +// @version 2.9.1 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -820,22 +820,24 @@ injectStyles(doc, 'cmk-sk-inv-btn-styles', ` .cmk-sk-inv-btn { - display: inline-block; - background: #2c6fad; - color: #fff !important; - font-size: 9px; - font-weight: bold; - padding: 1px 5px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + background: transparent; + color: #6aabde !important; + border: 1px solid #4a8fc4; border-radius: 3px; text-decoration: none !important; margin-left: 5px; vertical-align: middle; - font-family: monospace; - white-space: nowrap; cursor: pointer; - opacity: 0.85; + opacity: 0.75; + flex-shrink: 0; } - .cmk-sk-inv-btn:hover { opacity: 1; background: #1a5a99 !important; } + .cmk-sk-inv-btn:hover { opacity: 1; border-color: #88c4f0; color: #88c4f0 !important; } + .cmk-sk-inv-btn svg { display: block; } `); hostCells.forEach(td => { @@ -851,7 +853,7 @@ btn.target = '_blank'; btn.rel = 'noopener'; btn.title = `Service Discovery: ${hostname}`; - btn.textContent = 'disco'; + btn.innerHTML = ''; td.appendChild(btn); }); From 9d9cc3f041b6a3b29448043b0aed75f1c11b439e Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 17:39:30 +0200 Subject: [PATCH 14/46] fix: pulsante inventory con icona monitor+spunta arancione prima del nome host Sostituita lente con icona monitor+checkmark, colore arancione #e5a500, posizionata prima del nome host (prepend invece di append). bump version to 2.9.2 --- user_script/checkmk_swissknife.user.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index aee1d76..bb57390 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.9.1 +// @version 2.9.2 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -825,18 +825,18 @@ justify-content: center; width: 16px; height: 16px; - background: transparent; - color: #6aabde !important; - border: 1px solid #4a8fc4; + background: rgba(229,165,0,0.08); + color: #e5a500 !important; + border: 1px solid #e5a500; border-radius: 3px; text-decoration: none !important; - margin-left: 5px; + margin-right: 5px; vertical-align: middle; cursor: pointer; - opacity: 0.75; + opacity: 0.8; flex-shrink: 0; } - .cmk-sk-inv-btn:hover { opacity: 1; border-color: #88c4f0; color: #88c4f0 !important; } + .cmk-sk-inv-btn:hover { opacity: 1; background: rgba(229,165,0,0.18) !important; } .cmk-sk-inv-btn svg { display: block; } `); @@ -853,8 +853,8 @@ btn.target = '_blank'; btn.rel = 'noopener'; btn.title = `Service Discovery: ${hostname}`; - btn.innerHTML = ''; - td.appendChild(btn); + btn.innerHTML = ''; + td.prepend(btn); }); doc.body.dataset.cmkInventoryBtns = '1'; From b3f2ff08e30dfe4ec0701a0928845b59a7d1aad8 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 17:50:01 +0200 Subject: [PATCH 15/46] feat: aggiungi pulsante copia hostname in clipboard su view.py Accanto al pulsante Service Discovery, aggiunge un secondo pulsante con icona clipboard che copia il nome host negli appunti al click. Feedback visivo verde per 900ms al click confermato. bump version to 2.9.3 --- user_script/checkmk_swissknife.user.js | 37 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index bb57390..1b41ffe 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.9.2 +// @version 2.9.3 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -837,7 +837,26 @@ flex-shrink: 0; } .cmk-sk-inv-btn:hover { opacity: 1; background: rgba(229,165,0,0.18) !important; } - .cmk-sk-inv-btn svg { display: block; } + .cmk-sk-copy-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + background: transparent; + color: #999 !important; + border: 1px solid #666; + border-radius: 3px; + margin-right: 5px; + vertical-align: middle; + cursor: pointer; + opacity: 0.7; + flex-shrink: 0; + padding: 0; + } + .cmk-sk-copy-btn:hover { opacity: 1; border-color: #aaa; color: #ccc !important; } + .cmk-sk-copy-btn.copied { border-color: #4caf50 !important; color: #4caf50 !important; opacity: 1; } + .cmk-sk-inv-btn svg, .cmk-sk-copy-btn svg { display: block; } `); hostCells.forEach(td => { @@ -854,6 +873,20 @@ btn.rel = 'noopener'; btn.title = `Service Discovery: ${hostname}`; btn.innerHTML = ''; + + const copyBtn = doc.createElement('button'); + copyBtn.className = 'cmk-sk-copy-btn'; + copyBtn.type = 'button'; + copyBtn.title = `Copia hostname: ${hostname}`; + copyBtn.innerHTML = ''; + copyBtn.addEventListener('click', () => { + navigator.clipboard.writeText(hostname).then(() => { + copyBtn.classList.add('copied'); + setTimeout(() => copyBtn.classList.remove('copied'), 900); + }); + }); + + td.prepend(copyBtn); td.prepend(btn); }); From 291d96e29be925ec7a6d30ba90ad07be098427f4 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 17:52:54 +0200 Subject: [PATCH 16/46] fix: icona clipboard blu e spacing uniforme tra i due pulsanti host Colore clipboard cambiato da grigio a #5ab4d6 (blu). Ridotto il gap tra le due icone a 2px (da 5px) per raggrupparle visivamente, mantenendo 5px di distanza dal nome host. bump version to 2.9.4 --- user_script/checkmk_swissknife.user.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 1b41ffe..80ae85f 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.9.3 +// @version 2.9.4 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -830,7 +830,7 @@ border: 1px solid #e5a500; border-radius: 3px; text-decoration: none !important; - margin-right: 5px; + margin-right: 2px; vertical-align: middle; cursor: pointer; opacity: 0.8; @@ -843,19 +843,19 @@ justify-content: center; width: 16px; height: 16px; - background: transparent; - color: #999 !important; - border: 1px solid #666; + background: rgba(90,180,214,0.08); + color: #5ab4d6 !important; + border: 1px solid #5ab4d6; border-radius: 3px; margin-right: 5px; vertical-align: middle; cursor: pointer; - opacity: 0.7; + opacity: 0.8; flex-shrink: 0; padding: 0; } - .cmk-sk-copy-btn:hover { opacity: 1; border-color: #aaa; color: #ccc !important; } - .cmk-sk-copy-btn.copied { border-color: #4caf50 !important; color: #4caf50 !important; opacity: 1; } + .cmk-sk-copy-btn:hover { opacity: 1; background: rgba(90,180,214,0.18) !important; } + .cmk-sk-copy-btn.copied { border-color: #4caf50 !important; color: #4caf50 !important; background: rgba(76,175,80,0.12) !important; opacity: 1; } .cmk-sk-inv-btn svg, .cmk-sk-copy-btn svg { display: block; } `); From 2cb29441cdc7556be021af9b0419b279f55ef63e Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 17:55:49 +0200 Subject: [PATCH 17/46] feat: aggiungi terzo pulsante copia hostname corto e refactor spacing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Terzo pulsante viola copia l'hostname senza dominio (prima parte prima del primo punto). I tre pulsanti sono ora in un wrapper flex con gap:2px e margin-right:4px verso il nome host, eliminando le irregolarità di spacing tra icone inline. bump version to 2.9.5 --- user_script/checkmk_swissknife.user.js | 79 +++++++++++++++++--------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index 80ae85f..be536a4 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.9.4 +// @version 2.9.5 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -819,52 +819,65 @@ if (!hostCells.length) return; injectStyles(doc, 'cmk-sk-inv-btn-styles', ` - .cmk-sk-inv-btn { + .cmk-sk-btn-group { display: inline-flex; align-items: center; - justify-content: center; - width: 16px; - height: 16px; - background: rgba(229,165,0,0.08); - color: #e5a500 !important; - border: 1px solid #e5a500; - border-radius: 3px; - text-decoration: none !important; - margin-right: 2px; + gap: 2px; + margin-right: 4px; vertical-align: middle; - cursor: pointer; - opacity: 0.8; - flex-shrink: 0; } - .cmk-sk-inv-btn:hover { opacity: 1; background: rgba(229,165,0,0.18) !important; } - .cmk-sk-copy-btn { + .cmk-sk-inv-btn, .cmk-sk-copy-btn, .cmk-sk-copy-short-btn { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; - background: rgba(90,180,214,0.08); - color: #5ab4d6 !important; - border: 1px solid #5ab4d6; border-radius: 3px; - margin-right: 5px; - vertical-align: middle; cursor: pointer; opacity: 0.8; flex-shrink: 0; padding: 0; + text-decoration: none !important; + } + .cmk-sk-inv-btn, .cmk-sk-inv-btn:visited { + background: rgba(229,165,0,0.08); + color: #e5a500 !important; + border: 1px solid #e5a500; + } + .cmk-sk-inv-btn:hover { opacity: 1; background: rgba(229,165,0,0.18) !important; } + .cmk-sk-copy-btn { + background: rgba(90,180,214,0.08); + color: #5ab4d6 !important; + border: 1px solid #5ab4d6; } .cmk-sk-copy-btn:hover { opacity: 1; background: rgba(90,180,214,0.18) !important; } - .cmk-sk-copy-btn.copied { border-color: #4caf50 !important; color: #4caf50 !important; background: rgba(76,175,80,0.12) !important; opacity: 1; } - .cmk-sk-inv-btn svg, .cmk-sk-copy-btn svg { display: block; } + .cmk-sk-copy-short-btn { + background: rgba(160,120,200,0.08); + color: #a078c8 !important; + border: 1px solid #a078c8; + } + .cmk-sk-copy-short-btn:hover { opacity: 1; background: rgba(160,120,200,0.18) !important; } + .cmk-sk-copy-btn.copied, .cmk-sk-copy-short-btn.copied { + border-color: #4caf50 !important; + color: #4caf50 !important; + background: rgba(76,175,80,0.12) !important; + opacity: 1; + } + .cmk-sk-btn-group svg { display: block; } `); + const CLIP_SVG = ''; + hostCells.forEach(td => { const link = td.querySelector('a[href*="view_name=hoststatus"]'); if (!link) return; const params = new URLSearchParams(link.getAttribute('href').split('?')[1] || ''); const hostname = params.get('host'); if (!hostname) return; + const shortname = hostname.split('.')[0]; + + const group = doc.createElement('span'); + group.className = 'cmk-sk-btn-group'; const btn = doc.createElement('a'); btn.className = 'cmk-sk-inv-btn'; @@ -878,7 +891,7 @@ copyBtn.className = 'cmk-sk-copy-btn'; copyBtn.type = 'button'; copyBtn.title = `Copia hostname: ${hostname}`; - copyBtn.innerHTML = ''; + copyBtn.innerHTML = CLIP_SVG; copyBtn.addEventListener('click', () => { navigator.clipboard.writeText(hostname).then(() => { copyBtn.classList.add('copied'); @@ -886,8 +899,22 @@ }); }); - td.prepend(copyBtn); - td.prepend(btn); + const copyShortBtn = doc.createElement('button'); + copyShortBtn.className = 'cmk-sk-copy-short-btn'; + copyShortBtn.type = 'button'; + copyShortBtn.title = `Copia hostname corto: ${shortname}`; + copyShortBtn.innerHTML = CLIP_SVG; + copyShortBtn.addEventListener('click', () => { + navigator.clipboard.writeText(shortname).then(() => { + copyShortBtn.classList.add('copied'); + setTimeout(() => copyShortBtn.classList.remove('copied'), 900); + }); + }); + + group.appendChild(btn); + group.appendChild(copyBtn); + group.appendChild(copyShortBtn); + td.prepend(group); }); doc.body.dataset.cmkInventoryBtns = '1'; From 48f3bc49d4c392ccd6646745c9235a9d5a9eb140 Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 18:00:36 +0200 Subject: [PATCH 18/46] v2.9.6 - Fix spaziatura terzo pulsante azioni host in view.py Aggiunto margin: 0 ai pulsanti .cmk-sk-inv-btn/.cmk-sk-copy-btn/.cmk-sk-copy-short-btn per azzerare il margin di default del browser sui button element e ottenere spaziatura uniforme (gap: 2px) tra tutte e tre le icone nel gruppo. --- user_script/checkmk_swissknife.user.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/user_script/checkmk_swissknife.user.js b/user_script/checkmk_swissknife.user.js index be536a4..a15ce23 100644 --- a/user_script/checkmk_swissknife.user.js +++ b/user_script/checkmk_swissknife.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Checkmk SwissKnife // @namespace https://luigidacunto.com/ -// @version 2.9.5 +// @version 2.9.6 // @description Raccolta di miglioramenti all'interfaccia di Checkmk WATO. Ogni fix o enhancement viene aggiunto qui come feature indipendente. // @author Luigi D'Acunto // @homepageURL https://git.luigidacunto.com/tools/checkmk-swissknife @@ -837,6 +837,7 @@ opacity: 0.8; flex-shrink: 0; padding: 0; + margin: 0; text-decoration: none !important; } .cmk-sk-inv-btn, .cmk-sk-inv-btn:visited { From fc42b129dd9d07e56245d9913b24c25f2ead4dbe Mon Sep 17 00:00:00 2001 From: Luigi D'Acunto Date: Wed, 17 Jun 2026 18:10:13 +0200 Subject: [PATCH 19/46] v2.9.7 - Converti pulsante discovery da a