From e69e6b4019345a49018bdaab00dcadce5491f69d Mon Sep 17 00:00:00 2001 From: Erwin Dondorp Date: Sat, 11 Apr 2026 04:37:24 +0200 Subject: [PATCH] Also allow selection of keys and nodegroups --- docs/README.md | 11 +- saltgui/static/scripts/CommandBox.js | 109 ++++++++- .../output/OutputHighstateSummarySaltGui.js | 1 + saltgui/static/scripts/panels/Beacons.js | 11 +- saltgui/static/scripts/panels/Grains.js | 11 +- saltgui/static/scripts/panels/HighState.js | 13 +- saltgui/static/scripts/panels/Job.js | 4 + saltgui/static/scripts/panels/Keys.js | 215 +++++++++++++++- saltgui/static/scripts/panels/Login.js | 2 + saltgui/static/scripts/panels/Minions.js | 17 +- saltgui/static/scripts/panels/Nodegroups.js | 33 ++- saltgui/static/scripts/panels/Panel.js | 229 ++++++++++++------ saltgui/static/scripts/panels/Pillars.js | 11 +- saltgui/static/scripts/panels/Schedules.js | 13 +- 14 files changed, 535 insertions(+), 145 deletions(-) diff --git a/docs/README.md b/docs/README.md index e343ff6a3..ceac2c613 100644 --- a/docs/README.md +++ b/docs/README.md @@ -49,7 +49,7 @@ We suggest to upgrade the SaltStack installation when you are still using a vers - Keyboard control to apply templates - Choose between live info and cached info for grains/pillar - View details of orchestrations and allow to start them -- Act on multiple minions by first selecting them +- Act on multiple minions/keys/nodegroups by first selecting them ## Quick start using PAM as authentication method @@ -401,12 +401,15 @@ Each issue has its own dropdown-menu, which typically contains: But note that there might be more possible solutions, some of which may actually be more preferred. * A navigation-command to go to a page for more details. -## Minion selection +## Row selection Pages that show a simple list of minions allow individual minions to be selected. +The Keys page allows individual keys to be selected. +The NodeGroups page allows individual nodegroups and/or minions to be selected. + Use panel button [✔] to show an extra column with checkboxes in the table. -Minions can be selected one-by-one or you can use select-all, select-none by clicking on the column header. +Rows can be selected one-by-one or you can use select-all, select-none by clicking on the column header. The selection can be inverted by using CTRL-click. -The list of selected minions will be used in the command-box and for commands from a panel-menu. +The list of selected rows will be used in the command-box and for commands from a panel-menu. When the column is hidden, the selection-values are just ignored. ## Command documentation diff --git a/saltgui/static/scripts/CommandBox.js b/saltgui/static/scripts/CommandBox.js index acd34be47..c803a4c7e 100644 --- a/saltgui/static/scripts/CommandBox.js +++ b/saltgui/static/scripts/CommandBox.js @@ -423,24 +423,93 @@ export class CommandBox { button.disabled = false; } - static getSelectedMinionList () { + static _getNodegroupsSelection () { + const allNodeGroups = Utils.getStorageItemObject("session", "nodegroups"); + const allNodeGroupsKeys = Object.keys(allNodeGroups); + + let ret = ""; + const lst_nodegroups = Utils.getStorageItem("session", "select_nodegroups", null); + if (lst_nodegroups) { + for (const nodegroup of lst_nodegroups.split(",")) { + if (nodegroup === "null") { + // the spaces around '(' and before ')' are mandatory + // salt-api returns 500-InternalServerError when missing + // see also https://docs.saltproject.io/en/latest/topics/targeting/compound.html#precedence-matching + ret += " or not ( " + let grplst = ""; + for (const grp of allNodeGroupsKeys) { + grplst += " or N@" + grp; + } + // strip the prefix " or " + ret += grplst.substring(4) + " )"; + } else if (nodegroup) { + ret += " or N@" + nodegroup; + } + } + } + return ret; + } + + static _getMinionSelection () { + let ret = ""; + const lst_minions = Utils.getStorageItem("session", "select_minions", null); + if (lst_minions) { + let minionlist = ""; + for (const minion of lst_minions.split(",")) { + if (minion) { + minionlist += "," + minion; + } + } + if (minionlist) { + // substring removes the extra "," + ret += " or L@" + minionlist.substring(1); + } + } + return ret; + } + + static getSelectedItemList (pSessionKeys) { const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); if (!selectVisible) { return null; } - // only when the selection is visible - const selectMinions = Utils.getStorageItem("session", "select_minions", ""); - const lst = selectMinions.split(",").sort(); - while (lst.length > 0 && lst[0] === "") { - lst.shift(); + let target = ""; + + if (pSessionKeys.includes("select_nodegroups")) { + target += CommandBox._getNodegroupsSelection(); } - // and only when there is a selection - if (lst.length == 0) { + + if (pSessionKeys.includes("select_minions")) { + target += CommandBox._getMinionSelection(); + } + + if (pSessionKeys.includes("select_keys")) { + const lst_keys = Utils.getStorageItem("session", "select_keys", null); + if (lst_keys) { + let keylist = ""; + for (const key of lst_keys.split(",")) { + if (key) { + keylist += "," + key; + } + } + // substring removes the extra "," + target += " or " + keylist.substring(1); + } + } + + // remove the extra " or " + target = target.substring(4); + if (target.startsWith("L@")) { + // simplify when we only have the list of minions + target = target.substring(2); + } + + if (target === "") { return null; } - return lst.join(","); + return target; } static showManualRun (pApi) { @@ -498,7 +567,26 @@ export class CommandBox { CommandBox._populateTemplateTmplMenu(); CommandBox._populateTestProviders(pApi); - const lst = CommandBox.getSelectedMinionList() + let lst = null; + // different pages have different selection-types + // but run-command is available from every page + switch (window.location.hash) { + case "#minions": + case "#grains": + case "#schedules": + case "#pillars": + case "#beacons": + case "#highstate": + lst = CommandBox.getSelectedItemList(["select_minions"]); + break; + case "#keys": + lst = CommandBox.getSelectedItemList(["select_keys"]); + break; + case "#nodegroups": + lst = CommandBox.getSelectedItemList(["select_minions", "select_nodegroups"]); + break; + } + if (lst) { const targetField = document.getElementById("target"); targetField.value = lst; @@ -547,6 +635,7 @@ export class CommandBox { // The leading # was used to indicate a nodegroup if (pTargetType === "nodegroup" && pTarget.startsWith("#")) { + // remove the leading "#" as that is not part of the syntax for nodegroups pTarget = pTarget.substring(1); } diff --git a/saltgui/static/scripts/output/OutputHighstateSummarySaltGui.js b/saltgui/static/scripts/output/OutputHighstateSummarySaltGui.js index 6e544f6cd..caf5dd759 100644 --- a/saltgui/static/scripts/output/OutputHighstateSummarySaltGui.js +++ b/saltgui/static/scripts/output/OutputHighstateSummarySaltGui.js @@ -48,6 +48,7 @@ export class OutputHighstateSummarySaltGui { } if (line) { + // strip the prefix ", " const txtDiv = Utils.createDiv("", line.substring(2)); pDiv.append(txtDiv); } diff --git a/saltgui/static/scripts/panels/Beacons.js b/saltgui/static/scripts/panels/Beacons.js index 3c7646dc9..136f4356f 100644 --- a/saltgui/static/scripts/panels/Beacons.js +++ b/saltgui/static/scripts/panels/Beacons.js @@ -6,7 +6,7 @@ import {Utils} from "../Utils.js"; export class BeaconsPanel extends Panel { constructor () { - super("beacons"); + super("beacons", ["select_minions"]); this.addTitle("Beacons"); this.addSearchButton(); @@ -18,12 +18,11 @@ export class BeaconsPanel extends Panel { } onShow () { + super.onShow(); + const wheelKeyListAllPromise = this.api.getWheelKeyListAll(); const localBeaconsListPromise = this.api.getLocalBeaconsList(null); - const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); - this.showSelectColumn(selectVisible); - this.nrMinions = 0; wheelKeyListAllPromise.then((pWheelKeyListAllData) => { @@ -108,7 +107,7 @@ export class BeaconsPanel extends Panel { } updateOfflineMinion (pMinionId, pMinionsDict) { - super.updateOfflineMinion(pMinionId, pMinionsDict, true); + super.updateOfflineMinion(pMinionId, pMinionsDict); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); @@ -120,7 +119,7 @@ export class BeaconsPanel extends Panel { pMinionData = BeaconsPanel.fixBeaconsMinion(pMinionData); - super.updateMinion(null, pMinionId, pAllMinionsGrains, true); + super.updateMinion(null, pMinionId, pAllMinionsGrains); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); diff --git a/saltgui/static/scripts/panels/Grains.js b/saltgui/static/scripts/panels/Grains.js index 4adea352c..63af66474 100644 --- a/saltgui/static/scripts/panels/Grains.js +++ b/saltgui/static/scripts/panels/Grains.js @@ -7,7 +7,7 @@ import {Utils} from "../Utils.js"; export class GrainsPanel extends Panel { constructor () { - super("grains"); + super("grains", ["select_minions"]); this.addTitle("Grains"); this.addSearchButton(); @@ -27,8 +27,7 @@ export class GrainsPanel extends Panel { } onShow () { - const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); - this.showSelectColumn(selectVisible); + super.onShow(); if (this.previewColumsAdded !== true) { // collect the list of displayed extra grains @@ -86,7 +85,7 @@ export class GrainsPanel extends Panel { const minionIds = keys.minions.sort(); for (const minionId of minionIds) { - const minionTr = this.addMinion(minionId, true, this.previewGrains.length); + const minionTr = this.addMinion(minionId, this.previewGrains.length); // preliminary dropdown menu this._addMenuItemShowGrains(minionTr.dropdownmenu, minionId); @@ -105,7 +104,7 @@ export class GrainsPanel extends Panel { } updateOfflineMinion (pMinionId, pMinionsDict) { - super.updateOfflineMinion(pMinionId, pMinionsDict, true); + super.updateOfflineMinion(pMinionId, pMinionsDict); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); @@ -119,7 +118,7 @@ export class GrainsPanel extends Panel { } updateMinion (pMinionData, pMinionId, pAllMinionsGrains) { - super.updateMinion(pMinionData, pMinionId, pAllMinionsGrains, true); + super.updateMinion(pMinionData, pMinionId, pAllMinionsGrains); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); diff --git a/saltgui/static/scripts/panels/HighState.js b/saltgui/static/scripts/panels/HighState.js index 15ebe9394..70a22a318 100644 --- a/saltgui/static/scripts/panels/HighState.js +++ b/saltgui/static/scripts/panels/HighState.js @@ -12,7 +12,7 @@ import {Utils} from "../Utils.js"; export class HighStatePanel extends Panel { constructor () { - super("highstate"); + super("highstate", ["select_minions"]); // only consider this number of latest highstate jobs this._maxShowHighstates = Utils.getStorageItem("session", "max_show_highstates", 10); @@ -47,10 +47,9 @@ export class HighStatePanel extends Panel { } onShow () { - const wheelKeyListAllPromise = this.api.getWheelKeyListAll(); + super.onShow(); - const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); - this.showSelectColumn(selectVisible); + const wheelKeyListAllPromise = this.api.getWheelKeyListAll(); this.nrMinions = 0; @@ -100,14 +99,14 @@ export class HighStatePanel extends Panel { _addMenuItemStateApply (pMenu, pMinionId) { pMenu.addMenuItem("Apply state...", () => { const cmdArr = ["state.apply"]; - this.runCommand("", pMinionId, cmdArr, true); + this.runCommand("", pMinionId, cmdArr, ["select_minions"]); }); } _addMenuItemStateApplyTest (pMenu, pMinionId) { pMenu.addMenuItem("Test state...", () => { const cmdArr = ["state.apply", "test=", true]; - this.runCommand("", pMinionId, cmdArr, true); + this.runCommand("", pMinionId, cmdArr, ["select_minions"]); }); } @@ -366,7 +365,7 @@ export class HighStatePanel extends Panel { // we already have the TR // but this function also clears the row - this.getElement(trId, true); + this.getElement(trId, "select_minions", minionId); // mark the TR as populated minionTr.jid = pJobId; diff --git a/saltgui/static/scripts/panels/Job.js b/saltgui/static/scripts/panels/Job.js index 3ac0cfc0f..2cccbc1c2 100644 --- a/saltgui/static/scripts/panels/Job.js +++ b/saltgui/static/scripts/panels/Job.js @@ -372,6 +372,7 @@ export class JobPanel extends Panel { return null; } + // remove the first comma return minionList.substring(1); } @@ -419,6 +420,7 @@ export class JobPanel extends Panel { return null; } + // remove the first comma return minionList.substring(1); } @@ -452,6 +454,7 @@ export class JobPanel extends Panel { return null; } + // remove the first comma return minionList.substring(1); } @@ -485,6 +488,7 @@ export class JobPanel extends Panel { return null; } + // remove the first comma return minionList.substring(1); } diff --git a/saltgui/static/scripts/panels/Keys.js b/saltgui/static/scripts/panels/Keys.js index be4e17082..c56c85219 100644 --- a/saltgui/static/scripts/panels/Keys.js +++ b/saltgui/static/scripts/panels/Keys.js @@ -7,27 +7,37 @@ import {Utils} from "../Utils.js"; export class KeysPanel extends Panel { constructor () { - super("keys"); + super("keys", ["select_keys"]); this.addTitle("Keys"); this.addPanelMenu(); this._addPanelMenuItemWheelKeyAcceptAllUnaccepted(); + this._addPanelMenuItemWheelKeyAcceptSelectedUnaccepted(); this._addPanelMenuItemWheelKeyAcceptAllUnacceptedRejected(); + this._addPanelMenuItemWheelKeyAcceptSelectedUnacceptedRejected(); this._addPanelMenuItemWheelKeyAcceptAllUnacceptedDenied(); + this._addPanelMenuItemWheelKeyAcceptSelectedUnacceptedDenied(); this._addPanelMenuItemWheelKeyAcceptAllUnacceptedRejectedDenied(); + this._addPanelMenuItemWheelKeyAcceptSelectedUnacceptedRejectedDenied(); this._addPanelMenuItemWheelKeyRejectAllUnaccepted(); + this._addPanelMenuItemWheelKeyRejectSelectedUnaccepted(); this._addPanelMenuItemWheelKeyRejectAllUnacceptedAccepted(); + this._addPanelMenuItemWheelKeyRejectSelectedUnacceptedAccepted(); this._addPanelMenuItemWheelKeyRejectAllUnacceptedDenied(); + this._addPanelMenuItemWheelKeyRejectSelectedUnacceptedDenied(); this._addPanelMenuItemWheelKeyRejectAllUnacceptedAcceptedDenied(); + this._addPanelMenuItemWheelKeyRejectSelectedUnacceptedAcceptedDenied(); this._addPanelMenuItemWheelKeyDeleteAll(); + this._addPanelMenuItemWheelKeyDeleteSelected(); this.addSearchButton(); + this.addFilterButton(); this.addPlayPauseButton(); this.addHelpButton([ "The content of this page is", "automatically refreshed." ]); this.addWarningField(); - this.addTable(["-menu-", "Minion", "Status", "Fingerprint"], "data-list-keys"); + this.addTable(["-select-", "-menu-", "Minion", "Status", "Fingerprint"], "data-list-keys"); this.setTableSortable("Status", "asc"); this.addMsg(); @@ -37,13 +47,19 @@ export class KeysPanel extends Panel { } onShow () { + super.onShow(); + const wheelKeyListAllPromise = this.api.getWheelKeyListAll(); const wheelKeyFingerPromise = this.api.getWheelKeyFinger(); this.nrUnaccepted = 0; + this.nrUnacceptedSelected = 0; this.nrAccepted = 0; + this.nrAcceptedSelected = 0; this.nrDenied = 0; + this.nrDeniedSelected = 0; this.nrRejected = 0; + this.nrRejectedSelected = 0; this.showSyndicInfo(false); this.showClusterInfo(); @@ -216,6 +232,21 @@ export class KeysPanel extends Panel { "{0} " + key + " key", "{0} " + key + " keys"); } + + const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); + if (selectVisible) { + for (const tr of tbody.children) { + if (tr.firstChild.innerText !== Character.BALLOT_BOX_WITH_CHECK) { + continue; + } + const statusTd = tr.querySelector(".status"); + const statusText = statusTd.innerText + "-selected"; + if (cnt[statusText] === undefined) { + cnt[statusText] = 0; + } + cnt[statusText] += 1; + } + } } if (Object.keys(cnt).length === 0) { @@ -228,9 +259,13 @@ export class KeysPanel extends Panel { txt = txt.replace(/^no/, "No"); this.nrUnaccepted = cnt["unaccepted"]; + this.nrUnacceptedSelected = cnt["unaccepted-selected"]; this.nrAccepted = cnt["accepted"]; + this.nrAcceptedSelected = cnt["accepted-selected"]; this.nrDenied = cnt["denied"]; + this.nrDeniedSelected = cnt["denied-selected"]; this.nrRejected = cnt["rejected"]; + this.nrRejectedSelected = cnt["rejected-selected"]; super.updateFooter(txt, false); } @@ -269,7 +304,8 @@ export class KeysPanel extends Panel { } } - const minionIdTd = pMinionTr.querySelectorAll("td")[1]; + // td[0]=select, td[1]=menu, td[2]=name + const minionIdTd = pMinionTr.querySelectorAll("td")[2]; const minionIdSpan = minionIdTd.querySelector("span"); if (txt) { @@ -287,7 +323,9 @@ export class KeysPanel extends Panel { } _addAcceptedMinion (pMinionId, pMinionsDict) { - const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), false); + const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_keys", pMinionId); + minionTr.dataset.sessionKey = "select_keys"; + minionTr.dataset.selectKey = pMinionId; const minionIdTd = Utils.createTd(); const minionIdSpan = Utils.createSpan("minion-id", pMinionId); @@ -309,7 +347,9 @@ export class KeysPanel extends Panel { } _addRejectedMinion (pMinionId, pMinionsDict) { - const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), false); + const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_keys", pMinionId); + minionTr.dataset.sessionKey = "select_keys"; + minionTr.dataset.selectKey = pMinionId; const minionIdTd = Utils.createTd(); const minionIdSpan = Utils.createSpan("minion-id", pMinionId); @@ -334,7 +374,9 @@ export class KeysPanel extends Panel { } _addDeniedMinion (pMinionId, pMinionsDict) { - const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), false); + const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_keys", pMinionId); + minionTr.dataset.sessionKey = "select_keys"; + minionTr.dataset.selectKey = pMinionId; const minionIdTd = Utils.createTd(); const minionIdSpan = Utils.createSpan("minion-id", pMinionId); @@ -359,7 +401,9 @@ export class KeysPanel extends Panel { } _addPreMinion (pMinionId, pMinionsDict, pInsertAtTop = false) { - const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), false); + const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_keys", pMinionId); + minionTr.dataset.sessionKey = "select_keys"; + minionTr.dataset.selectKey = pMinionId; const minionIdTd = Utils.createTd(); const minionIdSpan = Utils.createSpan("minion-id", pMinionId); @@ -392,7 +436,9 @@ export class KeysPanel extends Panel { } _addMissingMinion (pMinionId, pMinionsDict) { - const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), false); + const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_keys", pMinionId); + minionTr.dataset.sessionKey = "select_keys"; + minionTr.dataset.selectKey = pMinionId; const minionIdTd = Utils.createTd(); const minionIdSpan = Utils.createSpan("minion-id", pMinionId); @@ -440,6 +486,9 @@ export class KeysPanel extends Panel { _addPanelMenuItemWheelKeyAcceptAllUnaccepted () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (this.nrUnaccepted > 0) { return "Accept all unaccepted keys..."; } @@ -450,8 +499,23 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyAcceptSelectedUnaccepted () { + this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected > 0) { + return "Accept selected unaccepted keys..."; + } + return null; + }, () => { + const cmdArr = ["wheel.key.accept"]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + _addPanelMenuItemWheelKeyAcceptAllUnacceptedRejected () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (!this.nrRejected) { return null; } @@ -465,8 +529,26 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyAcceptSelectedUnacceptedRejected () { + this.panelMenu.addMenuItem(() => { + if (!this.nrRejectedSelected) { + return null; + } + if (this.nrUnacceptedSelected > 0) { + return "Accept selected unaccepted+rejected keys..."; + } + return "Accept selected rejected keys..."; + }, () => { + const cmdArr = ["wheel.key.accept", "include_rejected=", true]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + _addPanelMenuItemWheelKeyAcceptAllUnacceptedDenied () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (!this.nrDenied) { return null; } @@ -480,8 +562,26 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyAcceptSelectedUnacceptedDenied () { + this.panelMenu.addMenuItem(() => { + if (!this.nrDeniedSelected) { + return null; + } + if (this.nrUnacceptedSelected > 0) { + return "Accept selected unaccepted+denied keys..."; + } + return "Accept selected denied keys..."; + }, () => { + const cmdArr = ["wheel.key.accept", "include_denied=", true]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + _addPanelMenuItemWheelKeyAcceptAllUnacceptedRejectedDenied () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (!this.nrRejected || !this.nrDenied) { return null; } @@ -495,6 +595,21 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyAcceptSelectedUnacceptedRejectedDenied () { + this.panelMenu.addMenuItem(() => { + if (!this.nrRejectedSelected || !this.nrDeniedSelected) { + return null; + } + if (this.nrUnacceptedSelected > 0) { + return "Accept selected unaccepted+denied+rejected keys..."; + } + return "Accept selected denied+rejected keys..."; + }, () => { + const cmdArr = ["wheel.key.accept", "include_denied=", true, "include_rejected=", true]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + _addMenuItemWheelKeyAccept2 (pMenu, pMinionId, pStatusField) { pMenu.addMenuItem(() => { const status = pStatusField.innerText; @@ -535,6 +650,9 @@ export class KeysPanel extends Panel { _addPanelMenuItemWheelKeyRejectAllUnaccepted () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (this.nrUnaccepted > 0) { return "Reject all unaccepted keys..."; } @@ -545,8 +663,23 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyRejectSelectedUnaccepted () { + this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelectd > 0) { + return "Reject selected unaccepted keys..."; + } + return null; + }, () => { + const cmdArr = ["wheel.key.reject"]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + _addPanelMenuItemWheelKeyRejectAllUnacceptedAccepted () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (!this.nrAccepted) { return null; } @@ -560,8 +693,26 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyRejectSelectedUnacceptedAccepted () { + this.panelMenu.addMenuItem(() => { + if (!this.nrAcceptedSelected) { + return null; + } + if (this.nrUnacceptedSelected > 0) { + return "Reject selected unaccepted+accepted keys..."; + } + return "Reject selected accepted keys..."; + }, () => { + const cmdArr = ["wheel.key.reject", "include_accepted=", true]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + _addPanelMenuItemWheelKeyRejectAllUnacceptedDenied () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (!this.nrDenied) { return null; } @@ -575,8 +726,26 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyRejectSelectedUnacceptedDenied () { + this.panelMenu.addMenuItem(() => { + if (!this.nrDeniedSelected) { + return null; + } + if (this.nrUnacceptedSelected > 0) { + return "Reject selected unaccepted+denied keys..."; + } + return "Reject selected denied keys..."; + }, () => { + const cmdArr = ["wheel.key.reject", "include_denied=", true]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + _addPanelMenuItemWheelKeyRejectAllUnacceptedAcceptedDenied () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (!this.nrAccepted || !this.nrDenied) { return null; } @@ -590,6 +759,21 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyRejectSelectedUnacceptedAcceptedDenied () { + this.panelMenu.addMenuItem(() => { + if (!this.nrAcceptedSelected || !this.nrDeniedSelected) { + return null; + } + if (this.nrUnacceptedSelected > 0) { + return "Reject selected unaccepted+accepted+denied keys..."; + } + return "Reject selected accepted+denied keys..."; + }, () => { + const cmdArr = ["wheel.key.reject", "include_accepted=", true, "include_denied=", true]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + _addMenuItemWheelKeyDelete (pMenu, pMinionId, pStatusField) { pMenu.addMenuItem(() => { const status = pStatusField.innerText; @@ -605,6 +789,9 @@ export class KeysPanel extends Panel { _addPanelMenuItemWheelKeyDeleteAll () { this.panelMenu.addMenuItem(() => { + if (this.nrUnacceptedSelected || this.nrAcceptedSelected || this.nrDeniedSelected || this.nrRejectedSelected) { + return null; + } if (this.nrAccepted > 0 || this.nrUnaccepted > 0 || this.nrRejected > 0 || this.nrDenied > 0) { return "Delete all keys..."; } @@ -615,6 +802,18 @@ export class KeysPanel extends Panel { }); } + _addPanelMenuItemWheelKeyDeleteSelected () { + this.panelMenu.addMenuItem(() => { + if (this.nrAcceptedSelected > 0 || this.nrUnacceptedSelected > 0 || this.nrRejectedSelected > 0 || this.nrDeniedSelected > 0) { + return "Delete selected keys..."; + } + return null; + }, () => { + const cmdArr = ["wheel.key.delete"]; + this.runCommand("", "*", cmdArr, ["select_keys"]); + }); + } + handleSaltAuthEvent (pData) { if (this.playOrPause !== "play") { diff --git a/saltgui/static/scripts/panels/Login.js b/saltgui/static/scripts/panels/Login.js index 4d324c991..74f8e62e3 100644 --- a/saltgui/static/scripts/panels/Login.js +++ b/saltgui/static/scripts/panels/Login.js @@ -498,6 +498,8 @@ export class LoginPanel extends Panel { Utils.setStorageItem("session", "select_visible", "false"); Utils.setStorageItem("session", "select_minions", ","); + Utils.setStorageItem("session", "select_keys", ","); + Utils.setStorageItem("session", "select_nodegroups", ","); const id = wheelConfigValuesData.id; const clusterId = wheelConfigValuesData.cluster_id; diff --git a/saltgui/static/scripts/panels/Minions.js b/saltgui/static/scripts/panels/Minions.js index af17b3b3a..542b9b46e 100644 --- a/saltgui/static/scripts/panels/Minions.js +++ b/saltgui/static/scripts/panels/Minions.js @@ -14,7 +14,7 @@ const MINION = 2; export class MinionsPanel extends Panel { constructor () { - super("minions"); + super("minions", ["select_minions"]); this.addTitle("Minions"); this.addPanelMenu(); @@ -30,6 +30,8 @@ export class MinionsPanel extends Panel { } onShow () { + super.onShow(); + this.nrMinions = 0; const useCacheGrains = Utils.getStorageItemBoolean("session", "use_cache_for_grains", false); @@ -43,9 +45,6 @@ export class MinionsPanel extends Panel { const runnerManageVersionsPromise = this.api.getRunnerManageVersions(); - const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); - this.showSelectColumn(selectVisible); - this.loadMinionsTxt(); wheelKeyListAllPromise.then((pWheelKeyListAllData) => { @@ -148,7 +147,7 @@ export class MinionsPanel extends Panel { // make it a list in compound notation notConnectedStr = "L@" + notConnectedStr.replace(/^,/, ""); } else { - // no need for list + // no need for list when it has just one element notConnectedStr = notConnectedStr.substring(2); } return "* and not " + notConnectedStr; @@ -184,7 +183,7 @@ export class MinionsPanel extends Panel { } updateOfflineMinion (pMinionId, pMinionsDict) { - super.updateOfflineMinion(pMinionId, pMinionsDict, true); + super.updateOfflineMinion(pMinionId, pMinionsDict); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); @@ -194,7 +193,7 @@ export class MinionsPanel extends Panel { } updateMinion (pMinionData, pMinionId, pAllMinionsGrains) { - super.updateMinion(pMinionData, pMinionId, pAllMinionsGrains, true); + super.updateMinion(pMinionData, pMinionId, pAllMinionsGrains); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); this._addMenuItemStateApply(minionTr.dropdownmenu, pMinionId); @@ -214,14 +213,14 @@ export class MinionsPanel extends Panel { _addMenuItemStateApply (pMenu, pMinionId) { pMenu.addMenuItem("Apply state...", () => { const cmdArr = ["state.apply"]; - this.runCommand("", pMinionId, cmdArr, true); + this.runCommand("", pMinionId, cmdArr, ["select_minions"]); }); } _addMenuItemStateApplyTest (pMenu, pMinionId) { pMenu.addMenuItem("Test state...", () => { const cmdArr = ["state.apply", "test=", true]; - this.runCommand("", pMinionId, cmdArr, true); + this.runCommand("", pMinionId, cmdArr, ["select_minions"]); }); } diff --git a/saltgui/static/scripts/panels/Nodegroups.js b/saltgui/static/scripts/panels/Nodegroups.js index 4506571f9..0af758483 100644 --- a/saltgui/static/scripts/panels/Nodegroups.js +++ b/saltgui/static/scripts/panels/Nodegroups.js @@ -8,21 +8,24 @@ import {Utils} from "../Utils.js"; export class NodegroupsPanel extends Panel { constructor () { - super("nodegroups"); + super("nodegroups", ["select_nodegroups", "select_minions"]); this.addTitle("Nodegroups"); this.addPanelMenu(); this._addMenuItemStateApplyMinion(this.panelMenu, "*"); this._addMenuItemStateApplyTestMinion(this.panelMenu, "*"); this.addSearchButton(); + this.addFilterButton(); this.addPlayPauseButton(); this.addWarningField(); - this.addTable(["-menu-", "Minion", "Status", "Salt version", "OS version"]); + this.addTable(["-select-", "-menu-", "Minion", "Status", "Salt version", "OS version"]); this.setTableClickable("cmd"); this.addMsg(); } onShow () { + super.onShow(); + this.nrMinions = 0; const useCacheGrains = Utils.getStorageItemBoolean("session", "use_cache_for_grains", false); @@ -79,7 +82,7 @@ export class NodegroupsPanel extends Panel { } updateOfflineMinion (pMinionId, pMinionsDict) { - super.updateOfflineMinion(pMinionId, pMinionsDict, false); + super.updateOfflineMinion(pMinionId, pMinionsDict); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); @@ -121,7 +124,7 @@ export class NodegroupsPanel extends Panel { Utils.addToolTip(minionSpan, "This minion is listed for this nodegroup,\nbut the minion is unknown", "bottom-left"); this.unknown += 1; } - minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), false); + minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_minions", pMinionId); minionTr.appendChild(minionTd); minionTr.appendChild(status); minionTr.appendChild(Utils.createTd()); @@ -136,6 +139,10 @@ export class NodegroupsPanel extends Panel { const minionTr2 = minionTr.cloneNode(true); nodegroupTr.parentNode.insertBefore(minionTr2, nodegroupTr.nextSibling); + // fix the select logic + const selectTd = minionTr2.querySelector("td"); + this._addSelectionCheckbox (minionTr2, "select_minions", selectTd); + // fix the click-to-copy-logic const addressSpan = minionTr2.querySelector("td.address span"); if (addressSpan) { @@ -156,7 +163,7 @@ export class NodegroupsPanel extends Panel { if (oldMenuButton) { oldMenuButton.parentElement.remove(); const newMenuButton = Utils.createTd(); - minionTr2.insertBefore(newMenuButton, minionTr2.firstChild); + minionTr2.insertBefore(newMenuButton, minionTr2.firstChild.nextSibling); minionTr2.dropdownmenu = new DropDownMenu(newMenuButton, "smaller"); if (minionIsOk) { this._addMenuItemStateApplyMinion(minionTr2.dropdownmenu, pMinionId); @@ -199,7 +206,7 @@ export class NodegroupsPanel extends Panel { this._moveMinionToNodegroup(minionId, nodegroup, pWheelKeyListAllSimpleData); } - const titleElement = this.table.querySelector("#ng-" + nodegroup + " td:nth-child(2)"); + const titleElement = this.table.querySelector("#ng-" + nodegroup + " td:nth-child(3)"); const cnt = nodelist.length; let txt = Utils.txtZeroOneMany(cnt, "no minions", cnt + " minion", cnt + " minions"); @@ -244,7 +251,7 @@ export class NodegroupsPanel extends Panel { this.setPlayPauseButton("none"); this.todoNodegroups = null; - const titleElement = this.table.querySelectorAll("#ng-" + null + " td")[1]; + const titleElement = this.table.querySelectorAll("#ng-" + null + " td")[2]; const cnt = this.table.rows.length - titleElement.parentElement.rowIndex - 1; if (cnt === 0) { @@ -308,8 +315,12 @@ export class NodegroupsPanel extends Panel { _addNodegroupRow (pNodegroup, pAllNodegroups) { const tr = Utils.createTr("no-search", null, "ng-" + pNodegroup); + tr.dataset.sessionKey = "select_nodegroups"; + tr.dataset.selectKey = pNodegroup; tr.style.borderTop = "4px double #ddd"; + this._addSelectionCheckbox (tr, "select_nodegroups"); + const menuTd = Utils.createTd(); tr.dropdownmenu = new DropDownMenu(menuTd, "smaller"); tr.appendChild(menuTd); @@ -346,7 +357,7 @@ export class NodegroupsPanel extends Panel { const minionIds = keys.minions.sort(); for (const minionId of minionIds) { - const minionTr = this.addMinion(minionId, false); + const minionTr = this.addMinion(minionId); // preliminary dropdown menu this._addMenuItemStateApplyMinion(minionTr.dropdownmenu, minionId); @@ -361,7 +372,7 @@ export class NodegroupsPanel extends Panel { } updateMinion (pMinionData, pMinionId, pAllNodegroupsGrains) { - super.updateMinion(pMinionData, pMinionId, pAllNodegroupsGrains, false); + super.updateMinion(pMinionData, pMinionId, pAllNodegroupsGrains); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); this._addMenuItemStateApplyMinion(minionTr.dropdownmenu, pMinionId); @@ -412,14 +423,14 @@ export class NodegroupsPanel extends Panel { _addMenuItemStateApplyMinion (pMenu, pMinionId) { pMenu.addMenuItem("Apply state...", () => { const cmdArr = ["state.apply"]; - this.runCommand("", pMinionId, cmdArr); + this.runCommand("", pMinionId, cmdArr, ["select_minions", "select_nodegroups"]); }); } _addMenuItemStateApplyTestMinion (pMenu, pMinionId) { pMenu.addMenuItem("Test state...", () => { const cmdArr = ["state.apply", "test=", true]; - this.runCommand("", pMinionId, cmdArr); + this.runCommand("", pMinionId, cmdArr, ["select_minions", "select_nodegroups"]); }); } diff --git a/saltgui/static/scripts/panels/Panel.js b/saltgui/static/scripts/panels/Panel.js index 3ddc01934..7c5b50be1 100644 --- a/saltgui/static/scripts/panels/Panel.js +++ b/saltgui/static/scripts/panels/Panel.js @@ -9,9 +9,10 @@ import {Utils} from "../Utils.js"; export class Panel { - constructor (pKey) { + constructor (pKey, pSelectionSessionKeys = []) { this.key = pKey; + this.selectionSessionKeys = pSelectionSessionKeys; const div = Utils.createDiv("panel", "", pKey + "-panel"); this.div = div; @@ -77,22 +78,14 @@ export class Panel { this.key + "-filter-button"); this.div.appendChild(span); span.addEventListener("click", (pClickEvent) => { + // remember the new state const selectVisible = !Utils.getStorageItemBoolean("session", "select_visible", false); Utils.setStorageItem("session", "select_visible", selectVisible); + // update the table this.showSelectColumn(selectVisible); - const tbody = this.table.tBodies[0]; - const selectMinions = Utils.getStorageItem("session", "select_minions", ""); - for (const tr of tbody.rows) { - const td = tr.children[0]; - if (selectMinions.includes("," + tr.dataset.minionId + ",")) { - td.innerText = Character.BALLOT_BOX_WITH_CHECK; - } else { - td.innerText = Character.BALLOT_BOX_UNCHECKED; - } - } - + // update the status line this.updateFooter(); - + // and stop pClickEvent.stopPropagation(); }); } @@ -202,52 +195,79 @@ export class Panel { } } - toggleSelection () { - let selectMinions = Utils.getStorageItem("session", "select_minions", ","); + _toggleSelection (pSessionKey) { + let selectedItems = Utils.getStorageItem("session", pSessionKey, ","); for (const tr of this.table.tBodies[0].children) { + if (tr.dataset.sessionKey !== pSessionKey) { + continue; + } const td = tr.children[0]; if (td.innerText === Character.BALLOT_BOX_UNCHECKED) { td.innerText = Character.BALLOT_BOX_WITH_CHECK; - selectMinions += tr.dataset.minionId + ","; + selectedItems += tr.dataset.selectKey + ","; } else { td.innerText = Character.BALLOT_BOX_UNCHECKED; - selectMinions = selectMinions.replace("," + tr.dataset.minionId + ",", ","); + selectedItems = selectedItems.replace("," + tr.dataset.selectKey + ",", ","); } } - Utils.setStorageItem("session", "select_minions", selectMinions); + Utils.setStorageItem("session", pSessionKey, selectedItems); this.updateFooter(); } - selectAllNone () { - const lst = CommandBox.getSelectedMinionList(); + _selectAll (pSessionKey) { + let selection = ","; - let selectAll; - if (lst === null) { - selectAll = true; - } else { - const nrSelected = lst.split(",").length; - selectAll = nrSelected !== this.nrMinions; + for (const tr of this.table.tBodies[0].children) { + if (tr.dataset.sessionKey !== pSessionKey) { + continue; + } + const td = tr.children[0]; + td.innerText = Character.BALLOT_BOX_WITH_CHECK; + if (!selection.includes("," + tr.dataset.selectKey + ",")) { + // prevent duplicated, e.g. for nodegroups + selection += tr.dataset.selectKey + ","; + } } - let selectMinions = ","; + Utils.setStorageItem("session", pSessionKey, selection); + + this.updateFooter(); + } + + _selectNone (pSessionKey) { for (const tr of this.table.tBodies[0].children) { - const td = tr.children[0]; - if (selectAll) { - td.innerText = Character.BALLOT_BOX_WITH_CHECK; - selectMinions += tr.dataset.minionId + ","; - } else { - td.innerText = Character.BALLOT_BOX_UNCHECKED; + if (tr.dataset.sessionKey !== pSessionKey) { + continue; } + const td = tr.children[0]; + td.innerText = Character.BALLOT_BOX_UNCHECKED; } - Utils.setStorageItem("session", "select_minions", selectMinions); + Utils.setStorageItem("session", pSessionKey, ","); this.updateFooter(); } + _selectAllNone (pSessionKey) { + for (const tr of this.table.tBodies[0].children) { + if (tr.dataset.sessionKey !== pSessionKey) { + continue; + } + const td = tr.children[0]; + if (td.innerText === Character.BALLOT_BOX_UNCHECKED) { + // at least one unchecked --> use select all + this._selectAll(pSessionKey); + return; + } + } + + // all were selected --> use select none + this._selectNone(pSessionKey); + } + addTable (pColumnNames, pFieldList = null) { const table = Utils.createElem("table", this.key, "", this.key + "-table"); @@ -257,12 +277,10 @@ export class Panel { tr.id = this.key + "-table-thead-tr"; const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); - this.usesSelect = false; for (const columnName of pColumnNames) { const th = Utils.createElem("th"); // e.g. "-summary-", "-help-", "-menu-" and "-select-" if (columnName === "-select-") { - this.usesSelect = true; th.innerText = Character.HEAVY_CHECK_MARK; th.classList.add("tooltip"); th.style.cursor = "pointer"; @@ -271,10 +289,12 @@ export class Panel { } Utils.addToolTip(th, "Click here to select all/none\nCTRL-click to invert selection", "bottom-left"); th.addEventListener("click", (pClickEvent) => { - if (pClickEvent.ctrlKey || pClickEvent.altKey) { - this.toggleSelection(); - } else { - this.selectAllNone(); + for (const selectKey of this.selectionSessionKeys) { + if (pClickEvent.ctrlKey || pClickEvent.altKey) { + this._toggleSelection(selectKey); + } else { + this._selectAllNone(selectKey); + } } pClickEvent.stopPropagation(); }); @@ -467,7 +487,7 @@ export class Panel { return true; } - addMinion (pMinionId, pUseSelect = true, freeColumns = 0) { + addMinion (pMinionId, freeColumns = 0) { let minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); if (minionTr !== null) { @@ -478,10 +498,12 @@ export class Panel { minionTr = Utils.createTr(); minionTr.id = Utils.getIdFromMinionId(pMinionId); minionTr.dataset.minionId = pMinionId; + minionTr.dataset.sessionKey = "select_minions"; + minionTr.dataset.selectKey = pMinionId; // optional select button - if (pUseSelect) { - this._addSelectionCheckbox (minionTr); + if (this.selectionSessionKeys.includes("select_minions")) { + this._addSelectionCheckbox (minionTr, "select_minions"); } // drop down menu @@ -508,11 +530,19 @@ export class Panel { return minionTr; } - _addSelectionCheckbox (pMinionTr) { - const selectTd = Utils.createTd("tooltip"); + _addSelectionCheckbox (pMinionTr, pSessionKey, pSelectTd = null) { + let selectTd; + if (pSelectTd) { + selectTd = pSelectTd; + } else { + selectTd = Utils.createTd(); + } + selectTd.style.cursor = "pointer"; + // one screen may have multiple selection methods, e.g. NodeGroups + selectTd._sessionKey = pSessionKey; - const selectMinions = Utils.getStorageItem("session", "select_minions", ","); - if (selectMinions.includes("," + pMinionTr.dataset.minionId + ",")) { + const selectedItems = Utils.getStorageItem("session", pSessionKey, ","); + if (selectedItems.includes("," + pMinionTr.dataset.selectKey + ",")) { selectTd.innerText = Character.BALLOT_BOX_WITH_CHECK; } else { selectTd.innerText = Character.BALLOT_BOX_UNCHECKED; @@ -524,27 +554,42 @@ export class Panel { } selectTd.addEventListener("click", (pClickEvent) => { - let selectMinions = Utils.getStorageItem("session", "select_minions", ","); + let selectedItems = Utils.getStorageItem("session", pSessionKey, ","); const tr = pClickEvent.target.parentElement; if (pClickEvent.target.innerText === Character.BALLOT_BOX_UNCHECKED) { pClickEvent.target.innerText = Character.BALLOT_BOX_WITH_CHECK; - selectMinions += tr.dataset.minionId + ","; + selectedItems += tr.dataset.selectKey + ","; } else { pClickEvent.target.innerText = Character.BALLOT_BOX_UNCHECKED; - selectMinions = selectMinions.replace("," + tr.dataset.minionId + ",", ","); - selectMinions = selectMinions.replace("," + tr.dataset.minionId + ",", ","); + selectedItems = selectedItems.replace("," + tr.dataset.selectKey + ",", ","); + } + Utils.setStorageItem("session", pSessionKey, selectedItems); + + // now adjust any duplicate rows (e.g. in nodegroups) + for (const tr2 of tr.parentElement.children) { + if (tr2 === tr) { + continue; + } + if (tr2.dataset.sessionKey !== tr.dataset.sessionKey) { + continue; + } + if (tr2.dataset.selectKey !== tr.dataset.selectKey) { + continue; + } + tr2.querySelector("td").innerText = pClickEvent.target.innerText; } - Utils.setStorageItem("session", "select_minions", selectMinions); this.updateFooter(); pClickEvent.stopPropagation(); }); - pMinionTr.appendChild(selectTd); + if (!pSelectTd) { + pMinionTr.appendChild(selectTd); + } } - getElement (id, pUseSelect) { + getElement (id, pSessionKey, pSelectKey) { let minionTr = this.table.querySelector("#" + id); if (minionTr === null) { @@ -560,9 +605,12 @@ export class Panel { minionTr.removeChild(minionTr.firstChild); } + minionTr.dataset.sessionKey = pSessionKey; + minionTr.dataset.selectKey = pSelectKey; + // (room for) selection box - if (pUseSelect) { - this._addSelectionCheckbox(minionTr); + if (this.selectionSessionKeys.includes(pSessionKey)) { + this._addSelectionCheckbox(minionTr, pSessionKey); } // drop down menu @@ -738,9 +786,9 @@ export class Panel { } } - updateMinion (pMinionData, pMinionId, pAllMinionsGrains, pUseSelect) { + updateMinion (pMinionData, pMinionId, pAllMinionsGrains) { - const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), pUseSelect); + const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_minions", pMinionId); const minionSpan = Utils.createSpan("minion-id", pMinionId); const minionTd = Utils.createTd(); @@ -864,6 +912,42 @@ export class Panel { this.updateFooter(); } + getFooterForNodeGroups () { + if (this.selectionSessionKeys.includes("select_nodegroups")) { + const lst_nodegroups = Utils.getStorageItem("session", "select_nodegroups", null); + if (lst_nodegroups !== null && lst_nodegroups !== ",") { + // so the "0" case actually does not occur here, adjust count for the extra ','s + const nrSelected = lst_nodegroups.split(",").length - 2; + return ", " + Utils.txtZeroOneMany(nrSelected, "no selected nodegroups", "{0} selected nodegroup", "{0} selected nodegroups"); + } + } + return ""; + } + + getFooterForMinions () { + if (this.selectionSessionKeys.includes("select_minions")) { + const lst_minions = Utils.getStorageItem("session", "select_minions", null); + if (lst_minions !== null && lst_minions !== ",") { + // so the "0" case actually does not occur here, adjust count for the extra ','s + const nrSelected = lst_minions.split(",").length - 2; + return ", " + Utils.txtZeroOneMany(nrSelected, "no selected minions", "{0} selected minion", "{0} selected minions"); + } + } + return ""; + } + + getFooterForKeys () { + if (this.selectionSessionKeys.includes("select_keys")) { + const lst_keys = Utils.getStorageItem("session", "select_keys", null); + if (lst_keys !== null && lst_keys !== ",") { + // so the "0" case actually does not occur here, adjust count for the extra ','s + const nrSelected = lst_keys.split(",").length - 2; + return ", " + Utils.txtZeroOneMany(nrSelected, "no selected keys", "{0} selected key", "{0} selected keys"); + } + } + return ""; + } + updateFooter (txt = "", showUnacceptedKeysCount = true) { if (this.nrMinions !== undefined) { @@ -882,6 +966,10 @@ export class Panel { txt += ", " + Utils.txtZeroOneMany(this.nrUnaccepted, "{0} unaccepted keys", "{0} unaccepted key", "{0} unaccepted keys"); } + txt += this.getFooterForNodeGroups(); + txt += this.getFooterForMinions(); + txt += this.getFooterForKeys(); + const noprint_b = ""; const noprint_e = ""; @@ -901,19 +989,13 @@ export class Panel { txt += noprint_e; } - const lst = CommandBox.getSelectedMinionList(); - if (lst !== null) { - const nrSelected = lst.split(",").length; - txt += ", " + Utils.txtZeroOneMany(nrSelected, "none selected", "{0} selected", "{0} selected"); - } - txt = txt.replace(/^, /g, ""); this.setMsg(txt.length > 0 ? txt : "(???)"); } - updateOfflineMinion (pMinionId, pMinionsDict, pUseSelect) { - const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), pUseSelect); + updateOfflineMinion (pMinionId, pMinionsDict) { + const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_minions", pMinionId); minionTr.appendChild(Utils.createTd("minion-id", pMinionId)); @@ -968,17 +1050,17 @@ export class Panel { return commandString; } - runCommand (pTargetType, pTargetString, pCommandString, pUseSelection = false) { + runCommand (pTargetType, pTargetString, pCommandString, pUseSelections = []) { if (typeof pCommandString !== "string") { // assume it is an array pCommandString = Panel.makeCommandString(pCommandString); } // replace with the selection if any - if (pUseSelection) { - const lst = CommandBox.getSelectedMinionList() - if (lst !== null) { - pTargetString = lst; + if (pUseSelections) { + const newtarget = CommandBox.getSelectedItemList(pUseSelections); + if (newtarget !== null) { + pTargetString = newtarget; } } @@ -1126,4 +1208,9 @@ export class Panel { td.style.display = pShow ? "" : "none"; } } + + onShow () { + const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); + this.showSelectColumn(selectVisible); + } } diff --git a/saltgui/static/scripts/panels/Pillars.js b/saltgui/static/scripts/panels/Pillars.js index d1e6de2ad..f406d449c 100644 --- a/saltgui/static/scripts/panels/Pillars.js +++ b/saltgui/static/scripts/panels/Pillars.js @@ -6,7 +6,7 @@ import {Utils} from "../Utils.js"; export class PillarsPanel extends Panel { constructor () { - super("pillars"); + super("pillars", ["select_minions"]); this.addTitle("Pillars"); this.addSearchButton(); @@ -19,15 +19,14 @@ export class PillarsPanel extends Panel { } onShow () { + super.onShow(); + const useCachePillar = Utils.getStorageItemBoolean("session", "use_cache_for_pillar", false); this.setWarningText("info", useCachePillar ? "the content of this screen is based on cached grains info, minion status or pillar info may not be accurate" : ""); const wheelKeyListAllPromise = this.api.getWheelKeyListAll(); const localPillarObfuscatePromise = useCachePillar ? this.api.getRunnerCachePillar(null) : this.api.getLocalPillarObfuscate(null); - const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); - this.showSelectColumn(selectVisible); - this.nrMinions = 0; wheelKeyListAllPromise.then((pWheelKeyListAllData) => { @@ -74,7 +73,7 @@ export class PillarsPanel extends Panel { } updateOfflineMinion (pMinionId, pMinionsDict) { - super.updateOfflineMinion(pMinionId, pMinionsDict, true); + super.updateOfflineMinion(pMinionId, pMinionsDict); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); @@ -83,7 +82,7 @@ export class PillarsPanel extends Panel { } updateMinion (pMinionData, pMinionId, pAllMinionsGrains) { - super.updateMinion(null, pMinionId, pAllMinionsGrains, true); + super.updateMinion(null, pMinionId, pAllMinionsGrains); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); diff --git a/saltgui/static/scripts/panels/Schedules.js b/saltgui/static/scripts/panels/Schedules.js index 53f510dae..09c4c92f3 100644 --- a/saltgui/static/scripts/panels/Schedules.js +++ b/saltgui/static/scripts/panels/Schedules.js @@ -6,7 +6,7 @@ import {Utils} from "../Utils.js"; export class SchedulesPanel extends Panel { constructor () { - super("schedules"); + super("schedules", ["select_minions"]); this.addTitle("Schedules"); this.addSearchButton(); @@ -18,12 +18,11 @@ export class SchedulesPanel extends Panel { } onShow () { + super.onShow(); + const wheelKeyListAllPromise = this.api.getWheelKeyListAll(); const localScheduleListPromise = this.api.getLocalScheduleList(null); - const selectVisible = Utils.getStorageItemBoolean("session", "select_visible", false); - this.showSelectColumn(selectVisible); - this.nrMinions = 0; wheelKeyListAllPromise.then((pWheelKeyListAllData) => { @@ -104,7 +103,7 @@ export class SchedulesPanel extends Panel { } updateOfflineMinion (pMinionId, pMinionsDict) { - super.updateOfflineMinion(pMinionId, pMinionsDict, true); + super.updateOfflineMinion(pMinionId, pMinionsDict); const minionTr = this.table.querySelector("#" + Utils.getIdFromMinionId(pMinionId)); @@ -116,9 +115,9 @@ export class SchedulesPanel extends Panel { pMinionData = SchedulesPanel.fixSchedulesMinion(pMinionData); - super.updateMinion(pMinionData, pMinionId, pAllMinionsGrains, true); + super.updateMinion(pMinionData, pMinionId, pAllMinionsGrains); - const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), true); + const minionTr = this.getElement(Utils.getIdFromMinionId(pMinionId), "select_minions", pMinionId); minionTr.appendChild(Utils.createTd("minion-id", pMinionId));