Skip to content

Commit c5c41b7

Browse files
authored
Merge pull request #545 from erwindon/keyboard-shortcuts
feature request: keyboard shortcuts, ESC key
2 parents 6cd1298 + 705f928 commit c5c41b7

11 files changed

Lines changed: 237 additions & 90 deletions

File tree

docs/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ such as `yubico_client`, or execution modules such as `boto3_sns`.
3838
- Define your own custom documentation for commands
3939
- Match list of minions against reference list
4040
- Match status of minions against reference list
41+
- Keyboard control for top-level navigation
42+
- Keyboard control to apply templates
4143

4244

4345
## Quick start using PAM as authentication method
@@ -184,6 +186,7 @@ saltgui_templates:
184186
description: First template
185187
target: "*"
186188
command: test.fib num=10
189+
key: "f"
187190
template2:
188191
description: Second template
189192
targettype: glob
@@ -213,6 +216,9 @@ When at least one template is assigned to a category, then you can select a temp
213216
selecting the actual category. Otherwise that choice remains hidden. Templates can be in multiple categories
214217
when a list of categories is assigned.
215218

219+
When at least one template is assigned to a key, then you can select a template by typing that key
220+
while using any other screen in SaltGUI. Otherwise that choice remains hidden.
221+
216222

217223
## Jobs
218224
SaltGUI shows a maximum of 7 jobs in on the right-hand-side of the screen.

saltgui/index.html

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
<body>
2525
<header id="header" class="no-print">
2626
<h1 class='logo'><div id="logo">SaltGUI</div></h1>
27-
<svg class='fab' width="36" height="36">
28-
<circle id='button-manual-run' cx="18" cy="18" r="18" fill="#4caf50" />
29-
<text style='pointer-events: none' fill="white" font-weight="bold" font-size="20" x="6" y="23">&gt;_</text>
30-
</svg>
27+
<h1 class='fab'>
28+
<svg width="36" height="36">
29+
<circle id='button-manual-run' cx="18" cy="18" r="18" fill="#4caf50" />
30+
<text style='pointer-events: none' fill="white" font-weight="bold" font-size="20" x="6" y="23">&gt;_</text>
31+
</svg>
32+
</h1>
3133
<!-- 1F4D6 = OPEN BOOK -->
3234
<!-- FE0E = VARIATION SELECTOR-15 (render as text) -->
3335
<h1 id="docu" class='docu'>&#x1F4D6;&#xFE0E;</h1>
@@ -37,7 +39,7 @@ <h1 id="docu" class='docu'>&#x1F4D6;&#xFE0E;</h1>
3739
<div class="minimenu">
3840
<div class="dropdown">
3941
<!-- 2261 = MATHEMATICAL OPERATOR IDENTICAL TO (aka "hamburger") -->
40-
<div class="menu-item">&#x2261;</div>
42+
<div class="menu-item" id="minimenu-top">&#x2261;</div>
4143
<div class="dropdown-content"></div>
4244
</div>
4345
</div>

saltgui/static/scripts/CommandBox.js

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,26 @@ export class CommandBox {
8787
}
8888

8989
static _templateTmplMenuItemTitle (pTemplate) {
90+
let keyboardHint = "";
91+
if (pTemplate.key) {
92+
keyboardHint = Character.NO_BREAK_SPACE + "[" + pTemplate.key + "]";
93+
}
94+
9095
if (CommandBox.templateTmplMenu._templateCategory === null) {
9196
// "(all)" selected, return all
92-
return pTemplate.description;
97+
return pTemplate.description + keyboardHint;
9398
}
9499
if (CommandBox.templateTmplMenu._templateCategory === undefined && pTemplate.category === undefined && pTemplate.categories === undefined) {
95100
// no category selected, return templates without category
96-
return pTemplate.description;
101+
return pTemplate.description + keyboardHint;
97102
}
98103
if (pTemplate.category && pTemplate.category === CommandBox.templateTmplMenu._templateCategory) {
99104
// item has one category, return when it matches
100-
return pTemplate.description;
105+
return pTemplate.description + keyboardHint;
101106
}
102107
if (pTemplate.categories && pTemplate.categories.indexOf(CommandBox.templateTmplMenu._templateCategory) >= 0) {
103108
// item has a list of categories, return when one matches
104-
return pTemplate.description;
109+
return pTemplate.description + keyboardHint;
105110
}
106111
return null;
107112
}
@@ -130,7 +135,7 @@ export class CommandBox {
130135
menu.addMenuItem(
131136
() => CommandBox._templateTmplMenuItemTitle(template),
132137
() => {
133-
CommandBox._applyTemplate(template);
138+
CommandBox.applyTemplateByTemplate(template);
134139
}
135140
);
136141
}
@@ -250,33 +255,44 @@ export class CommandBox {
250255
});
251256
}
252257

253-
static _applyTemplate (template) {
254-
255-
if (template.targettype) {
256-
let targetType = template.targettype;
258+
static applyTemplateByProperties (pTargetType, pTarget, pCommand) {
259+
if (pTargetType) {
257260
const targetbox = document.getElementById("target-box");
258261
// show the extended selection controls when
259262
targetbox.style.display = "inherit";
260-
if (targetType !== "glob" && targetType !== "list" && targetType !== "compound" && targetType !== "nodegroup") {
263+
if (pTargetType !== "glob" && pTargetType !== "list" && pTargetType !== "compound" && pTargetType !== "nodegroup") {
261264
// we don't support that, revert to standard (not default)
262-
targetType = "glob";
265+
pTargetType = "glob";
263266
}
264-
TargetType.setTargetType(targetType);
267+
TargetType.setTargetType(pTargetType);
265268
} else {
266269
// not in the template, revert to default
267270
TargetType.setTargetTypeDefault();
268271
}
269272

270-
if (template.target) {
273+
if (pTarget) {
271274
const targetField = document.getElementById("target");
272-
targetField.value = template.target;
275+
targetField.value = pTarget;
273276
TargetType.autoSelectTargetType(targetField.value);
274277
}
275278

276-
if (template.command) {
279+
if (pCommand) {
277280
const commandField = document.getElementById("command");
278-
commandField.value = template.command;
281+
commandField.value = pCommand;
282+
}
283+
}
284+
285+
static applyTemplateByTemplate (pTemplate) {
286+
CommandBox.applyTemplateByProperties(pTemplate.targettype, pTemplate.target, pTemplate.command);
287+
}
288+
289+
static applyTemplateByName (pTemplateName) {
290+
const templates = Utils.getStorageItemObject("session", "templates");
291+
const template = templates[pTemplateName];
292+
if (!template) {
293+
return;
279294
}
295+
CommandBox.applyTemplateByTemplate(template);
280296
}
281297

282298
static getScreenModifyingCommands () {

saltgui/static/scripts/Router.js

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export class Router {
6666
const logo = document.getElementById("logo");
6767
Utils.addToolTip(logo, "ctrl-click to see\nOptions and Stats", "logo");
6868

69+
const fab = document.querySelector(".fab");
70+
Utils.addToolTip(fab, "Type 'c' to show\nmanual run", "fab");
71+
6972
Router.updateMainMenu();
7073

7174
const hash = window.location.hash.replace(/^#/, "");
@@ -77,7 +80,13 @@ export class Router {
7780
/* eslint-enable compat/compat */
7881
}
7982

80-
_registerMenuItem (pParentId, pButtonId, pUrl) {
83+
_registerMenuItem (pParentId, pButtonId, pUrl, pKey) {
84+
85+
// shortcut
86+
87+
if (pKey) {
88+
Utils.setStorageItem("session", "menu_" + pKey, pUrl);
89+
}
8190

8291
// full menu
8392

@@ -97,17 +106,30 @@ export class Router {
97106
dropDownDiv.append(dropdownContent);
98107
}
99108
const itemDiv = Utils.createDiv("run-command-button menu-item", pButtonId, "button-" + pButtonId + "1");
109+
if (pKey) {
110+
// currently applies to all, but just in case
111+
itemDiv.classList.add("menu-item-first-letter");
112+
}
100113
dropdownContent.append(itemDiv);
101114
} else {
102115
const topItemDiv = Utils.createDiv("menu-item", pButtonId, "button-" + pButtonId + "1");
103116
dropDownDiv.append(topItemDiv);
117+
if (pKey) {
118+
topItemDiv.classList.add("menu-item-first-letter");
119+
}
104120
}
105121

106122
// mini menu
107123

108124
const miniMenuDiv = document.querySelector(".minimenu");
109125
const dropdownContent2 = miniMenuDiv.querySelector(".dropdown-content");
110-
const menuItemDiv = Utils.createDiv("run-command-button menu-item", (pParentId ? "-" + Character.NO_BREAK_SPACE : "") + pButtonId, "button-" + pButtonId + "2");
126+
const menuItemDiv = Utils.createDiv("run-command-button menu-item", pButtonId, "button-" + pButtonId + "2");
127+
if (pParentId) {
128+
menuItemDiv.style.paddingLeft = "50px";
129+
}
130+
if (pKey) {
131+
menuItemDiv.classList.add("menu-item-first-letter");
132+
}
111133
dropdownContent2.append(menuItemDiv);
112134

113135
// activate the menu items as needed
@@ -177,19 +199,20 @@ export class Router {
177199
/* eslint-enable compat/compat */
178200
});
179201

180-
this._registerMenuItem(null, "minions", "minions");
181-
this._registerMenuItem("minions", "grains", "grains");
182-
this._registerMenuItem("minions", "schedules", "schedules");
183-
this._registerMenuItem("minions", "pillars", "pillars");
184-
this._registerMenuItem("minions", "beacons", "beacons");
185-
this._registerMenuItem("minions", "nodegroups", "nodegroups");
186-
this._registerMenuItem(null, "keys", "keys");
187-
this._registerMenuItem(null, "jobs", "jobs");
188-
this._registerMenuItem("jobs", "highstate", "highstate");
189-
this._registerMenuItem("jobs", "templates", "templates");
190-
this._registerMenuItem(null, "events", "events");
191-
this._registerMenuItem("events", "reactors", "reactors");
192-
this._registerMenuItem(null, "issues", "issues");
202+
this._registerMenuItem(null, "minions", "minions", "m");
203+
this._registerMenuItem("minions", "grains", "grains", "g");
204+
this._registerMenuItem("minions", "schedules", "schedules", "s");
205+
this._registerMenuItem("minions", "pillars", "pillars", "p");
206+
this._registerMenuItem("minions", "beacons", "beacons", "b");
207+
this._registerMenuItem("minions", "nodegroups", "nodegroups", "n");
208+
this._registerMenuItem(null, "keys", "keys", "k");
209+
this._registerMenuItem(null, "jobs", "jobs", "j");
210+
this._registerMenuItem("jobs", "highstate", "highstate", "h");
211+
this._registerMenuItem("jobs", "templates", "templates", "t");
212+
this._registerMenuItem(null, "events", "events", "e");
213+
this._registerMenuItem("events", "reactors", "reactors", "r");
214+
this._registerMenuItem(null, "issues", "issues", "i");
215+
// no shortcut for logout
193216
this._registerMenuItem(null, "logout", "logout");
194217
}
195218

saltgui/static/scripts/Utils.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,4 +646,31 @@ export class Utils {
646646
ddc.style.display = pHide ? "none" : "";
647647
}
648648
}
649+
650+
static isValidKeyUpEvent (pKeyUpEvent) {
651+
if (pKeyUpEvent.altKey) {
652+
return false;
653+
}
654+
if (pKeyUpEvent.ctrlKey) {
655+
return false;
656+
}
657+
if (pKeyUpEvent.metaKey) {
658+
return false;
659+
}
660+
if (pKeyUpEvent.shiftKey) {
661+
return false;
662+
}
663+
if (pKeyUpEvent.isComposing) {
664+
return false;
665+
}
666+
if (pKeyUpEvent.target !== document.body) {
667+
// not when focus is in a text field
668+
return false;
669+
}
670+
if (pKeyUpEvent.key.length > 1) {
671+
// not a simple key
672+
return false;
673+
}
674+
return true;
675+
}
649676
}

saltgui/static/scripts/pages/Page.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/* global */
22

3+
import {CommandBox} from "../CommandBox.js";
4+
import {Router} from "../Router.js";
35
import {Utils} from "../Utils.js";
46

57
export class Page {
@@ -29,6 +31,53 @@ export class Page {
2931

3032
this.panels = [];
3133
this.api = pRouter.api;
34+
35+
const body = document.querySelector("body");
36+
body.onkeyup = (keyUpEvent) => {
37+
if (!Utils.isValidKeyUpEvent(keyUpEvent)) {
38+
return;
39+
}
40+
41+
if (this._handleTemplateKey(keyUpEvent)) {
42+
keyUpEvent.stopPropagation();
43+
return;
44+
}
45+
46+
if (this._handleMenuKey(keyUpEvent)) {
47+
keyUpEvent.stopPropagation();
48+
// return;
49+
}
50+
};
51+
}
52+
53+
_handleTemplateKey (keyUpEvent) {
54+
const templateName = Utils.getStorageItem("session", "template_" + keyUpEvent.key, "");
55+
if (templateName === "") {
56+
// key not bound to a template
57+
return false;
58+
}
59+
60+
// apply template
61+
CommandBox.applyTemplateByName(templateName);
62+
CommandBox.showManualRun(this.api);
63+
return true;
64+
}
65+
66+
_handleMenuKey (keyUpEvent) {
67+
if (keyUpEvent.key === "c") {
68+
CommandBox.showManualRun(this.api);
69+
return true;
70+
}
71+
72+
const pages = Router._getPagesList();
73+
const page = Utils.getStorageItem("session", "menu_" + keyUpEvent.key, "");
74+
// Arrays.includes() is only available from ES7/2016
75+
if (page && (pages.length === 0 || pages.indexOf(page) >= 0)) {
76+
this.router.goTo(page);
77+
return true;
78+
}
79+
80+
return false;
3281
}
3382

3483
addPanel (pPanel) {

saltgui/static/scripts/panels/Login.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,13 @@ export class LoginPanel extends Panel {
345345
const templates = wheelConfigValuesData.saltgui_templates;
346346
Utils.setStorageItem("session", "templates", JSON.stringify(templates));
347347

348+
for (const templateName in templates) {
349+
const template = templates[templateName];
350+
if (template.key !== undefined) {
351+
Utils.setStorageItem("session", "template_" + template.key, templateName);
352+
}
353+
}
354+
348355
const reactors = wheelConfigValuesData.reactor;
349356
Utils.setStorageItem("session", "reactors", JSON.stringify(reactors));
350357

0 commit comments

Comments
 (0)