Skip to content

Commit

Permalink
Merge pull request #330 from erwindon/saltgui_pages
Browse files Browse the repository at this point in the history
Add support for configuring which pages are displayed on SaltGUI
  • Loading branch information
erwindon authored Feb 7, 2021
2 parents e63a19e + 7c044e0 commit 1c94cc6
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 101 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,27 @@ saltgui_public_pillars:
- pub_.*
```

## Reduced menus
When api's are disabled using the native `external_auth` mechanism,
SaltGUI may show menu-items that have become unuseable.
In that case, it may be useful to reduce the menu-bar to less items.
Variable `saltgui_pages` is read
from salt master configuration file `/etc/salt/master`.
It contains the list of accessible pages per user.
The first page in the list also becomes the landing page.
Users that are not listed still have the full menu.
e.g.:
```
saltgui_pages:
user1:
- keys
- grains
```
Note that this is NOT a security mechanism to reduce what a user can do.
All pages are still accessible using their original deep-link.
And also any command can still be issued using the command-box.
For real security measures, use parameter `external_auth`.

## Performance
SaltGUI does not have artificial restrictions.
But displaying all data may be slow when there is a lot of data.
Expand Down
42 changes: 3 additions & 39 deletions saltgui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,10 @@
</head>

<body>
<header id="header">
<header id="header" style="height: 53px">
<h1 id="logo" class='logo'>SaltGUI</h1>
<!-- full menu -->
<div class="fullmenu">
<div class="dropdown">
<div class="menu-item" id="button-minions1">minions</div>
<div class="dropdown-content">
<div class="run-command-button menu-item" id='button-grains1'>grains</div>
<div class="run-command-button menu-item" id='button-schedules1'>schedules</div>
<div class="run-command-button menu-item" id='button-pillars1'>pillars</div>
<div class="run-command-button menu-item" id='button-beacons1'>beacons</div>
</div>
</div>
<div class='menu-item' id='button-keys1'>keys</div>
<div class="dropdown">
<div class="menu-item" id="button-jobs1">jobs</div>
<div class="dropdown-content">
<div class='run-command-button menu-item' id='button-templates1'>templates</div>
</div>
</div>
<div class="dropdown">
<div class='menu-item' id='button-events1'>events</div>
<div class="dropdown-content">
<div class='run-command-button menu-item' id='button-reactors1'>reactors</div>
</div>
</div>
<div class='menu-item' id='button-logout1'>logout</div>
</div>
<div class="fullmenu"></div>
<!-- mini menu -->
<svg class='fab' width="36" height="36">
<circle id='button-manual-run' cx="18" cy="18" r="18" fill="#4caf50" />
Expand All @@ -58,19 +34,7 @@ <h1 id="logo" class='logo'>SaltGUI</h1>
<div class="dropdown">
<!-- 2261 = MATHEMATICAL OPERATOR IDENTICAL TO (aka "hamburger") -->
<div class="menu-item">&#x2261;</div>
<div class="dropdown-content">
<div class="run-command-button menu-item" id="button-minions2">minions</div>
<div class="run-command-button menu-item" id='button-grains2'>-&nbsp;grains</div>
<div class="run-command-button menu-item" id='button-schedules2'>-&nbsp;schedules</div>
<div class="run-command-button menu-item" id='button-pillars2'>-&nbsp;pillars</div>
<div class="run-command-button menu-item" id='button-beacons2'>-&nbsp;beacons</div>
<div class='run-command-button menu-item' id='button-keys2'>keys</div>
<div class="run-command-button menu-item" id="button-jobs2">jobs</div>
<div class='run-command-button menu-item' id='button-templates2'>-&nbsp;templates</div>
<div class="run-command-button menu-item" id="button-events2">events</div>
<div class="run-command-button menu-item" id="button-reactors2">-&nbsp;reactors</div>
<div class='run-command-button menu-item' id='button-logout2'>logout</div>
</div>
<div class="dropdown-content"></div>
</div>
</div>
<div id="warning" style="display: none"></div>
Expand Down
205 changes: 151 additions & 54 deletions saltgui/static/scripts/Router.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class Router {

this._registerRouterEventListeners();

this.updateMainMenu();
Router.updateMainMenu();

const hash = window.location.hash.replace(/^#/, "");
const search = window.location.search;
Expand All @@ -66,21 +66,51 @@ export class Router {
/* eslint-enable compat/compat */
}

_registerMenuItem (pButtonId, pHash) {
_registerMenuItem (pParentId, pButtonId, pUrl) {

// full menu

const fullMenuDiv = document.querySelector(".fullmenu");

const dropDownName = pParentId || pButtonId;
let dropDownDiv = document.getElementById("dropdown-" + dropDownName);
if (!dropDownDiv) {
dropDownDiv = Utils.createDiv("dropdown", "", "dropdown-" + dropDownName);
fullMenuDiv.append(dropDownDiv);
}

if (pParentId) {
let dropdownContent = document.getElementById("dropdown-content-" + pParentId);
if (!dropdownContent) {
dropdownContent = Utils.createDiv("dropdown-content", "", "dropdown-content-" + pParentId);
dropDownDiv.append(dropdownContent);
}
const itemDiv = Utils.createDiv("run-command-button menu-item", pButtonId, "button-" + pButtonId + "1");
dropdownContent.append(itemDiv);
} else {
const topItemDiv = Utils.createDiv("menu-item", pButtonId, "button-" + pButtonId + "1");
dropDownDiv.append(topItemDiv);
}

// mini menu

const miniMenuDiv = document.querySelector(".minimenu");
const dropdownContent2 = miniMenuDiv.querySelector(".dropdown-content");
// 00A0 = NO-BREAK SPACE
const menuItemDiv = Utils.createDiv("run-command-button menu-item", (pParentId ? "-\u00A0" : "") + pButtonId, "button-" + pButtonId + "2");
dropdownContent2.append(menuItemDiv);

// activate the menu items as needed

// conditions go inside the handler because the pages
// data may still being retrieved at this point
for (const nr of ["1", "2"]) {
document.getElementById("button-" + pButtonId + nr).
addEventListener("click", (pClickEvent) => {
const panel = pClickEvent.target.parentElement;
if (panel.classList.contains("dropdown-content")) {
// temporarily hide the panel, won't be visible again
// after 500ms because the mouseover caused it to show
// also clicking toplevel button cannot do the same
panel.style.display = "none";
setTimeout(() => {
panel.style.display = "";
}, 500);
addEventListener("click", () => {
const pages = Router._getPagesList();
if (pUrl && (pButtonId === "logout" || pages.length === 0 || pages.includes(pButtonId))) {
this.goTo(pUrl);
}
this.goTo(pHash);
});
}
}
Expand All @@ -105,17 +135,17 @@ export class Router {
/* eslint-enable compat/compat */
});

this._registerMenuItem("minions", "");
this._registerMenuItem("grains", "grains");
this._registerMenuItem("schedules", "schedules");
this._registerMenuItem("pillars", "pillars");
this._registerMenuItem("beacons", "beacons");
this._registerMenuItem("keys", "keys");
this._registerMenuItem("jobs", "jobs");
this._registerMenuItem("templates", "templates");
this._registerMenuItem("events", "eventsview");
this._registerMenuItem("reactors", "reactors");
this._registerMenuItem("logout", "logout");
this._registerMenuItem(null, "minions", "minions");
this._registerMenuItem("minions", "grains", "grains");
this._registerMenuItem("minions", "schedules", "schedules");
this._registerMenuItem("minions", "pillars", "pillars");
this._registerMenuItem("minions", "beacons", "beacons");
this._registerMenuItem(null, "keys", "keys");
this._registerMenuItem(null, "jobs", "jobs");
this._registerMenuItem("jobs", "templates", "templates");
this._registerMenuItem(null, "events", "eventsview");
this._registerMenuItem("events", "reactors", "reactors");
this._registerMenuItem(null, "logout", "logout");
}

_registerPage (pPage) {
Expand All @@ -125,22 +155,88 @@ export class Router {
}
}

updateMainMenu () {
for (const page of this.pages) {
const visible = page.constructor.isVisible();
for (const item of [page.menuItemElement1, page.menuItemElement2]) {
if (!item) {
// This page does not have a menu item
// e.g. login-page or grains-minion page
} else if (visible) {
item.classList.remove("menu-item-hidden");
} else {
item.classList.add("menu-item-hidden");
}
static _getUserName () {
const loginResponseStr = Utils.getStorageItem("session", "login-response", "{}");
try {
const loginResponse = JSON.parse(loginResponseStr);
return loginResponse.user;
} catch (err) {
console.error("error in object login-response=" + loginResponseStr + " --> " + err.name + ": " + err.message);
return null;
}
}

static _getPagesList () {
const pagesText = Utils.getStorageItem("session", "pages", "{}");
let pages;
try {
pages = JSON.parse(pagesText);
} catch (err) {
console.error("error in object saltgui_pages=" + pagesText + " --> " + err.name + ": " + err.message);
return {};
}
const userName = Router._getUserName();
if (!userName || typeof pages !== "object" || !(userName in pages)) {
return [];
}
const ret = pages[userName];
if (!ret || ret[0] === "*") {
return [];
}
return ret;
}

static _showMenuItem (pPages, pName, pChildren = []) {
// assume the best
let visible = true;

// do not show unwanted menu items
if (pPages.length && !pPages.includes(pName)) {
visible = false;
}

// force visibility of the logout menuitem
if (pName === "logout") {
visible = true;
}

// still show a menu item when a child is visible
let hasVisibleChild = false;
for (const page of pChildren) {
if (pPages.includes(page)) {
hasVisibleChild = true;
break;
}
}

// perform the hiding/showing
for (let nr = 1; nr <= 2; nr++) {
const item = document.getElementById("button-" + pName + nr);
item.style.color = !visible && hasVisibleChild ? "lightgray" : "black";
if (visible || hasVisibleChild) {
item.classList.remove("menu-item-hidden");
} else {
item.classList.add("menu-item-hidden");
}
}
}

static updateMainMenu () {
const pages = Router._getPagesList();

Router._showMenuItem(pages, "minions", ["grains", "schedules", "pillars", "beacons"]);
Router._showMenuItem(pages, "grains");
Router._showMenuItem(pages, "schedules");
Router._showMenuItem(pages, "pillars");
Router._showMenuItem(pages, "beacons");
Router._showMenuItem(pages, "keys");
Router._showMenuItem(pages, "jobs", ["templates"]);
Router._showMenuItem(pages, "templates");
Router._showMenuItem(pages, "events", ["reactors"]);
Router._showMenuItem(pages, "reactors");
Router._showMenuItem(pages, "logout");
}

// pForward = 0 --> normal navigation
// pForward = 1 --> back navigation using regular gui
// pForward = 2 --> back navigation using browser
Expand All @@ -153,6 +249,16 @@ export class Router {
pQuery = {"reason": "no-session"};
}

const pages = Router._getPagesList();
if (!pHash) {
// go to the concrete default page
if (pages.length) {
pHash = pages[0];
} else {
pHash = "minions";
}
}

// save the details from the parent
const parentHash = document.location.hash.replace(/^#/, "");
const search = window.location.search;
Expand Down Expand Up @@ -201,6 +307,7 @@ export class Router {
this._showPage(route);
return;
}

// route could not be found
// just go to the main page
if (pHash === "") {
Expand All @@ -215,35 +322,25 @@ export class Router {

pPage.pageElement.style.display = "";

// de-activate all menu items
const activeMenuItems = Array.from(document.querySelectorAll(".menu-item-active"));
activeMenuItems.forEach((menuItem) => {
menuItem.classList.remove("menu-item-active");
});

const elem1 = pPage.menuItemElement1;
// highlight the fullmenu item
const elem1 = document.getElementById(pPage.menuItemElement1);
if (elem1) {
elem1.classList.add("menu-item-active");
const parentItem = elem1.parentElement.parentElement.firstChild;
// activate also parent menu item if child element is selected
if (elem1.id === "button-pillars1" ||
elem1.id === "button-schedules1" ||
elem1.id === "button-grains1" ||
elem1.id === "button-beacons1") {
const minionMenuItem = document.getElementById("button-minions1");
minionMenuItem.classList.add("menu-item-active");
}
if (elem1.id === "button-jobs1" ||
elem1.id === "button-templates1") {
const jobsMenuItem = document.getElementById("button-jobs1");
jobsMenuItem.classList.add("menu-item-active");
}
if (elem1.id === "button-events1" ||
elem1.id === "button-reactors1") {
const eventsMenuItem = document.getElementById("button-events1");
eventsMenuItem.classList.add("menu-item-active");
if (parentItem.id.startsWith("button-")) {
parentItem.classList.add("menu-item-active");
}
}

const elem2 = pPage.menuItemElement2;
// highlight the minimenu item
const elem2 = document.getElementById(pPage.menuItemElement2);
if (elem2) {
elem2.classList.add("menu-item-active");
}
Expand Down
2 changes: 1 addition & 1 deletion saltgui/static/scripts/pages/Minions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {Page} from "./Page.js";
export class MinionsPage extends Page {

constructor (pRouter) {
super("", "Minions", "page-minions", "button-minions", pRouter);
super("minions", "Minions", "page-minions", "button-minions", pRouter);

this.minions = new MinionsPanel();
super.addPanel(this.minions);
Expand Down
4 changes: 2 additions & 2 deletions saltgui/static/scripts/pages/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export class Page {
this.pageElement = div;
this.router = pRouter;
if (pMenuItemSelector) {
this.menuItemElement1 = document.getElementById(pMenuItemSelector + "1");
this.menuItemElement2 = document.getElementById(pMenuItemSelector + "2");
this.menuItemElement1 = pMenuItemSelector + "1";
this.menuItemElement2 = pMenuItemSelector + "2";
}

this.panels = [];
Expand Down
Loading

0 comments on commit 1c94cc6

Please sign in to comment.