diff --git a/docs/README.md b/docs/README.md index e343ff6a3..55a74d9e7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -200,6 +200,11 @@ In all cases, a tooltip is added to a date+time field that shows the full repres When using very old browsers, the required date/time functions may not be present. In that case SaltGUI reverts to simply displaying the reported time from the Salt system. The tooltip is then not shown. +## Theme +SaltGUI follows the browser color-scheme preference. +When SaltGUI is embedded, it also uses theme hints from the parent frame when those are available. + + ## Templates SaltGUI supports command templates for easier command entry into the command-box. The menu item for that becomes visible there when you define one or more templates diff --git a/saltgui/index.html b/saltgui/index.html index c455b85af..22ff90cc2 100644 --- a/saltgui/index.html +++ b/saltgui/index.html @@ -3,6 +3,8 @@ SaltGUI + + @@ -26,8 +28,8 @@

- - >_ + + >_

diff --git a/saltgui/static/scripts/Character.js b/saltgui/static/scripts/Character.js index 46243d45e..4cfb7aa53 100644 --- a/saltgui/static/scripts/Character.js +++ b/saltgui/static/scripts/Character.js @@ -64,6 +64,6 @@ export class Character { } static buttonInText (txt) { - return " " + txt + " "; + return " " + txt + " "; } } diff --git a/saltgui/static/scripts/Router.js b/saltgui/static/scripts/Router.js index 017b57a4a..86b43cc94 100644 --- a/saltgui/static/scripts/Router.js +++ b/saltgui/static/scripts/Router.js @@ -40,7 +40,7 @@ export class Router { this.pages = []; Router.currentPage = undefined; - this._registerPage(new LoginPage(this)); + this._registerPage(Router.loginPage = new LoginPage(this)); this._registerPage(Router.minionsPage = new MinionsPage(this)); this._registerPage(Router.keysPage = new KeysPage(this)); this._registerPage(Router.grainsPage = new GrainsPage(this)); @@ -272,7 +272,7 @@ export class Router { // perform the hiding/showing for (let nr = 1; nr <= 2; nr++) { const item = document.getElementById("button-" + pPage.path + nr); - item.style.color = !visible && hasVisibleChild ? "lightgray" : "black"; + item.classList.toggle("menu-item-dimmed", !visible && hasVisibleChild); if (!visible) { // hide the shortcut indicator item.classList.remove("menu-item-first-letter"); diff --git a/saltgui/static/scripts/output/OutputDocumentation.js b/saltgui/static/scripts/output/OutputDocumentation.js index 839c86bc0..5def31632 100644 --- a/saltgui/static/scripts/output/OutputDocumentation.js +++ b/saltgui/static/scripts/output/OutputDocumentation.js @@ -270,12 +270,12 @@ export class OutputDocumentation { // replace ``......`` // e.g. in "sys.doc state.apply" // named groups are only introduced in ES9/2018 - out = out.replace(/``([^`]*)``/g, "$1"); + out = out.replace(/``([^`]*)``/g, "$1"); // replace `......` // e.g. in "sys.doc state.apply" // named groups are only introduced in ES9/2018 - out = out.replace(/`([^`]*)`/g, "$1"); + out = out.replace(/`([^`]*)`/g, "$1"); // remove whitespace at end of lines out = out.replace(/ *\n/gm, ""); diff --git a/saltgui/static/scripts/output/OutputHighstateSummaryOriginal.js b/saltgui/static/scripts/output/OutputHighstateSummaryOriginal.js index 5aada5b86..cc9f18cf2 100644 --- a/saltgui/static/scripts/output/OutputHighstateSummaryOriginal.js +++ b/saltgui/static/scripts/output/OutputHighstateSummaryOriginal.js @@ -11,8 +11,7 @@ export class OutputHighstateSummaryOriginal { let txt = "\nSummary for " + pMinionId; txt += "\n------------"; - const summarySpan = Utils.createSpan("", txt); - summarySpan.style.color = "aqua"; + const summarySpan = Utils.createSpan("text-info", txt); pDiv.append(summarySpan); const total = pSucceeded + pSkipped + pFailed; @@ -24,7 +23,6 @@ export class OutputHighstateSummaryOriginal { if (pChangesSummary > 0) { txt = " ("; const oSpan = Utils.createSpan("", txt); - oSpan.style.color = "white"; pDiv.append(oSpan); txt = "changed=" + pChangesSummary; @@ -33,7 +31,6 @@ export class OutputHighstateSummaryOriginal { txt = ")"; const cSpan = Utils.createSpan("", txt); - cSpan.style.color = "white"; pDiv.append(cSpan); } @@ -42,7 +39,7 @@ export class OutputHighstateSummaryOriginal { if (pFailed > 0) { failedSpan.classList.add("task-failure"); } else { - failedSpan.style.color = "aqua"; + failedSpan.classList.add("text-info"); } pDiv.append(failedSpan); @@ -57,7 +54,7 @@ export class OutputHighstateSummaryOriginal { if (pFailed > 0) { failureSpan.classList.add("task-failure"); } else { - failureSpan.style.color = "aqua"; + failureSpan.classList.add("text-info"); } pDiv.append(failureSpan); } @@ -65,8 +62,7 @@ export class OutputHighstateSummaryOriginal { txt = "\n------------"; txt += "\nTotal states run: " + total; txt += "\nTotal run time: " + Output.getDuration(pTotalMilliSeconds); - const totalsSpan = Utils.createSpan("", txt); - totalsSpan.style.color = "aqua"; + const totalsSpan = Utils.createSpan("text-info", txt); pDiv.append(totalsSpan); pDiv.style.cursor = "pointer"; } diff --git a/saltgui/static/scripts/panels/HighState.js b/saltgui/static/scripts/panels/HighState.js index 15ebe9394..2bef3bd16 100644 --- a/saltgui/static/scripts/panels/HighState.js +++ b/saltgui/static/scripts/panels/HighState.js @@ -440,7 +440,6 @@ export class HighStatePanel extends Panel { // for information (keys.length > this._maxHighstateStates) const span = Utils.createSpan("task"); - span.style.backgroundColor = "black"; // this also sets the span's class(es) Output._setTaskToolTip(span, data); @@ -516,7 +515,6 @@ export class HighStatePanel extends Panel { // remove the priority indicator from the key const itemSpan = Utils.createSpan(["tasksummary", className], character); - itemSpan.style.backgroundColor = "black"; summarySpan.append(itemSpan); Utils.addToolTip(itemSpan, className.replace("task-", "").replace("-", " with ")); } diff --git a/saltgui/static/scripts/panels/Jobs.js b/saltgui/static/scripts/panels/Jobs.js index 081a120b7..0de70a741 100644 --- a/saltgui/static/scripts/panels/Jobs.js +++ b/saltgui/static/scripts/panels/Jobs.js @@ -302,12 +302,13 @@ export class JobsPanel extends Panel { } if (newLevel > oldLevel) { span.dataset.level = newLevel; + span.classList.remove("text-success", "text-warning", "text-error"); if (newLevel === 1) { - span.style.color = "green"; + span.classList.add("text-success"); } else if (newLevel === 2) { - span.style.color = "orange"; + span.classList.add("text-warning"); } else if (newLevel === 3) { - span.style.color = "red"; + span.classList.add("text-error"); } } span.style.removeProperty("display"); diff --git a/saltgui/static/scripts/panels/JobsDetails.js b/saltgui/static/scripts/panels/JobsDetails.js index aab538a7e..7dcd9ed07 100644 --- a/saltgui/static/scripts/panels/JobsDetails.js +++ b/saltgui/static/scripts/panels/JobsDetails.js @@ -266,16 +266,16 @@ export class JobsDetailsPanel extends JobsPanel { if (pData.Minions.length === 0) { detailsHTML += ""; } else if (keyCount === pData.Minions.length) { - detailsHTML += ""; + detailsHTML += ""; } else { - detailsHTML += ""; + detailsHTML += ""; } detailsHTML += Utils.txtZeroOneMany(keyCount, "no results", "{0} result", "{0} results"); detailsHTML += ""; if (keyCount < pData.Minions.length) { - detailsHTML += ", "; + detailsHTML += ", "; detailsHTML += pData.Minions.length - keyCount; detailsHTML += " missing"; } @@ -299,13 +299,13 @@ export class JobsDetailsPanel extends JobsPanel { for (const key of keys) { detailsHTML += ", "; if (key === "0-0") { - detailsHTML += ""; + detailsHTML += ""; detailsHTML += Utils.txtZeroOneMany(summary[key], "", "{0} success", "{0} successes"); } else if (key.startsWith("0-")) { - detailsHTML += ""; + detailsHTML += ""; detailsHTML += Utils.txtZeroOneMany(summary[key], "", "{0} success", "{0} successes"); } else if (key.startsWith("1-")) { - detailsHTML += ""; + detailsHTML += ""; detailsHTML += Utils.txtZeroOneMany(summary[key], "", "{0} failure", "{0} failures"); } else { // if (key.startsWith("2-")) diff --git a/saltgui/static/scripts/panels/Login.js b/saltgui/static/scripts/panels/Login.js index 4d324c991..ebdf726a6 100644 --- a/saltgui/static/scripts/panels/Login.js +++ b/saltgui/static/scripts/panels/Login.js @@ -260,21 +260,21 @@ export class LoginPanel extends Panel { break; case "no-session": // gray because we cannot prove that the user was/wasnt logged in - this._showNoticeText("gray", "Not logged in", "notice_not_logged_in"); + this._showNoticeText("var(--color-notice-muted)", "Not logged in", "notice_not_logged_in"); break; case "session-cancelled": - this._showNoticeText("#F44336", "Session cancelled", "notice-session-cancelled"); + this._showNoticeText("var(--color-notice-danger)", "Session cancelled", "notice-session-cancelled"); break; case "session-expired": - this._showNoticeText("#F44336", "Session expired", "notice-session-expired"); + this._showNoticeText("var(--color-notice-danger)", "Session expired", "notice-session-expired"); break; case "logout": // gray because this is the result of a user action - this._showNoticeText("gray", "Logout", "notice_logout"); + this._showNoticeText("var(--color-notice-muted)", "Logout", "notice_logout"); break; default: // should not occur - this._showNoticeText("#F44336", reason, "notice_other:" + reason); + this._showNoticeText("var(--color-notice-danger)", reason, "notice_other:" + reason); } this._enableLoginControls(true); @@ -303,11 +303,40 @@ export class LoginPanel extends Panel { } _onLoginSuccess () { - this._showNoticeText("#4CAF50", "Please wait" + Character.HORIZONTAL_ELLIPSIS, "notice_please_wait"); + this._showNoticeText("var(--color-text-accent)", "Please wait" + Character.HORIZONTAL_ELLIPSIS, "notice_please_wait"); Utils.setStorageItem("local", "salt-motd-txt", ""); Utils.setStorageItem("local", "salt-motd-html", ""); + this.bootstrapSession(); + + // allow the success message to be seen + window.setTimeout(() => { + // erase credentials since we don't do page-refresh + this.usernameField.value = ""; + this.passwordField.value = ""; + if (Utils.getStorageItem("session", "login_response") !== null) { + // we might have been logged out in this first second + // e.g. when clock between client and server differs more than the session timout + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.get("page")) { + // a redirect page is specified + const params = {}; + for (const pair of urlParams.entries()) { + params[pair[0]] = pair[1]; + } + const page = params["page"]; + delete params["page"]; + this.router.goTo(page, params); + } else { + this.router.goTo(""); + } + } + }, 1000); + + } + + bootstrapSession () { // We need these functions to populate the dropdown boxes const wheelConfigValuesPromise = this.api.getWheelConfigValues(); const runnerStateOrchestrateShowSlsPromise = this.api.getRunnerStateOrchestrateShowSls(); @@ -339,30 +368,6 @@ export class LoginPanel extends Panel { }); /* eslint-enable no-unused-vars */ - // allow the success message to be seen - window.setTimeout(() => { - // erase credentials since we don't do page-refresh - this.usernameField.value = ""; - this.passwordField.value = ""; - if (Utils.getStorageItem("session", "login_response") !== null) { - // we might have been logged out in this first second - // e.g. when clock between client and server differs more than the session timout - const urlParams = new URLSearchParams(window.location.search); - if (urlParams.get("page")) { - // a redirect page is specified - const params = {}; - for (const pair of urlParams.entries()) { - params[pair[0]] = pair[1]; - } - const page = params["page"]; - delete params["page"]; - this.router.goTo(page, params); - } else { - this.router.goTo(""); - } - } - }, 1000); - BeaconsMinionPanel.getAvailableBeacons(this.api); } @@ -517,19 +522,19 @@ export class LoginPanel extends Panel { _onLoginFailure (error) { if (typeof error === "string") { // something detected before trying to login - this._showNoticeText("#F44336", error, "notice_login_string_error"); + this._showNoticeText("var(--color-notice-danger)", error, "notice_login_string_error"); } else if (error && error.status === 503) { // Service Unavailable // e.g. salt-api running but salt-master not running - this._showNoticeText("#F44336", error.message, "notice_login_service_unavailable"); + this._showNoticeText("var(--color-notice-danger)", error.message, "notice_login_service_unavailable"); } else if (error && error.status === -1) { // No permissions: login valid, but no api functions executable // e.g. PAM says OK and /etc/salt/master says NO - this._showNoticeText("#F44336", error.message, "notice_login_other_error"); + this._showNoticeText("var(--color-notice-danger)", error.message, "notice_login_other_error"); } else if (error.toString().startsWith("TypeError: NetworkError")) { - this._showNoticeText("#F44336", "Network Error", "notice_login_other_error"); + this._showNoticeText("var(--color-notice-danger)", "Network Error", "notice_login_other_error"); } else { - this._showNoticeText("#F44336", "Authentication failed", "notice_auth_failed"); + this._showNoticeText("var(--color-notice-danger)", "Authentication failed", "notice_auth_failed"); } this._enableLoginControls(true); diff --git a/saltgui/static/scripts/panels/Nodegroups.js b/saltgui/static/scripts/panels/Nodegroups.js index 4506571f9..c141207a4 100644 --- a/saltgui/static/scripts/panels/Nodegroups.js +++ b/saltgui/static/scripts/panels/Nodegroups.js @@ -308,7 +308,7 @@ export class NodegroupsPanel extends Panel { _addNodegroupRow (pNodegroup, pAllNodegroups) { const tr = Utils.createTr("no-search", null, "ng-" + pNodegroup); - tr.style.borderTop = "4px double #ddd"; + tr.style.borderTop = "4px double var(--color-border-default)"; const menuTd = Utils.createTd(); tr.dropdownmenu = new DropDownMenu(menuTd, "smaller"); diff --git a/saltgui/static/scripts/panels/Stats.js b/saltgui/static/scripts/panels/Stats.js index a51bd5183..445ebeb57 100644 --- a/saltgui/static/scripts/panels/Stats.js +++ b/saltgui/static/scripts/panels/Stats.js @@ -69,7 +69,7 @@ export class StatsPanel extends Panel { _handleStats (pStatsData) { if (this.showErrorRowInstead(pStatsData)) { - this.statsTd.innerHTML = "this error is typically caused by using the collect_stats: True setting in the master configuration file, which is broken in at least the recent versions of salt-api"; + this.statsTd.innerHTML = "this error is typically caused by using the collect_stats: True setting in the master configuration file, which is broken in at least the recent versions of salt-api"; window.clearInterval(this.updateStatsInterval); this.updateStatsInterval = null; return; diff --git a/saltgui/static/scripts/theme.js b/saltgui/static/scripts/theme.js new file mode 100644 index 000000000..a3c383b39 --- /dev/null +++ b/saltgui/static/scripts/theme.js @@ -0,0 +1,20 @@ +(function initializeTheme () { + const context = globalThis; + const root = document.documentElement; + const mediaQuery = context.matchMedia ? context.matchMedia("(prefers-color-scheme: dark)") : null; + + function wantsDarkTheme () { + return mediaQuery ? mediaQuery.matches : false; + } + + function applyTheme () { + root.dataset.theme = wantsDarkTheme() ? "dark" : "light"; + } + + applyTheme(); + if (mediaQuery && typeof mediaQuery.addEventListener === "function") { + mediaQuery.addEventListener("change", applyTheme); + } else if (mediaQuery && typeof mediaQuery.addListener === "function") { + mediaQuery.addListener(applyTheme); + } +})(); diff --git a/saltgui/static/stylesheets/beacons.css b/saltgui/static/stylesheets/beacons.css index 0a6c3f902..9f606e139 100644 --- a/saltgui/static/stylesheets/beacons.css +++ b/saltgui/static/stylesheets/beacons.css @@ -16,5 +16,5 @@ .beacon-disabled, .beacon-waiting { - color: gray; + color: var(--color-text-muted); } diff --git a/saltgui/static/stylesheets/controls.css b/saltgui/static/stylesheets/controls.css index 1e473f6e5..ec2b7ce50 100644 --- a/saltgui/static/stylesheets/controls.css +++ b/saltgui/static/stylesheets/controls.css @@ -2,7 +2,8 @@ input, select { - color: #272727; + background-color: var(--color-background-control); + color: var(--color-text-strong); padding: 7px 10px; display: inline-block; margin-bottom: 15px; @@ -14,24 +15,24 @@ select { input[type="text"], input[type="password"], select { - border: 2px solid #e2e2e2; + border: 2px solid var(--color-border-control); border-radius: 2px; } input[type="text"]:focus, input[type="password"]:focus, select:focus { - border: 2px solid #4caf50; + border: 2px solid var(--color-border-accent); } input[type="submit"] { - background-color: #4caf50; - color: white; + background-color: var(--color-text-accent); + color: var(--color-text-inverse); border: 0; margin-top: 5px; margin-bottom: 0; cursor: pointer; - box-shadow: 0 0 5px rgba(33, 33, 33, 50%); + box-shadow: var(--color-shadow-button); width: 20%; min-width: 200px; } diff --git a/saltgui/static/stylesheets/dropdown.css b/saltgui/static/stylesheets/dropdown.css index 49b8d7c59..7cee2e488 100644 --- a/saltgui/static/stylesheets/dropdown.css +++ b/saltgui/static/stylesheets/dropdown.css @@ -13,12 +13,13 @@ div.search-box .menu-dropdown { position: absolute; max-height: 300px; overflow-y: auto; - background: #fff; - box-shadow: 0 3px 10px -2px rgba(0, 0, 0, 30%); - border: 1px solid rgba(0, 0, 0, 10%); + background: var(--color-background-panel); + box-shadow: var(--color-shadow-dropdown); + border: 1px solid var(--color-border-default); z-index: 5; cursor: pointer; text-align: left; + color: var(--color-text-primary); } /* Links inside the menu-dropdown */ @@ -37,7 +38,7 @@ div.search-box .menu-dropdown { /* Change color of menu-dropdown links on hover */ .run-command-button .menu-dropdown-content div.run-command-button:hover { - background: rgba(0, 0, 0, 15%); + background: var(--color-background-hover); cursor: pointer; } @@ -60,8 +61,8 @@ pre.output .run-command-button { } pre.output .run-command-button:hover .menu-dropdown { - background-color: #f9f9f9; - color: black; + background-color: var(--color-background-control-soft); + color: var(--color-text-default); } .dropdown { @@ -72,8 +73,8 @@ pre.output .run-command-button:hover .menu-dropdown { .dropdown-content { display: none; position: absolute; - background-color: #f9f9f9; - box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 20%); + background-color: var(--color-background-control-soft); + box-shadow: var(--color-shadow-dropdown-large); z-index: 3; } diff --git a/saltgui/static/stylesheets/job.css b/saltgui/static/stylesheets/job.css index 6a38a4e0d..8c3adf262 100644 --- a/saltgui/static/stylesheets/job.css +++ b/saltgui/static/stylesheets/job.css @@ -17,7 +17,7 @@ } .highlight-task { - background-color: gray; + background-color: var(--color-background-highlight); } #summary-list-job { diff --git a/saltgui/static/stylesheets/login.css b/saltgui/static/stylesheets/login.css index d5529bb35..77e44881b 100644 --- a/saltgui/static/stylesheets/login.css +++ b/saltgui/static/stylesheets/login.css @@ -9,14 +9,14 @@ @media not print { #page-login { - background-color: #263238; + background-color: var(--color-background-page); } } #login-panel { align-self: center; - background-color: white; - box-shadow: 0 0 24px rgba(0, 0, 0, 70%); + background-color: var(--color-background-panel); + box-shadow: var(--color-shadow-panel); border-radius: 2px; /* 1px needed to prevent bottom margin to disappear when using small screens */ @@ -30,7 +30,7 @@ font-weight: lighter; font-size: 60px; width: 100%; - color: #505050; + color: var(--color-text-primary); } #login-panel input { @@ -39,13 +39,26 @@ font-size: 18px; } +#login-panel input, +#login-panel select { + background-color: var(--color-background-control); + color: var(--color-text-strong); + caret-color: var(--color-text-strong); + -webkit-text-fill-color: var(--color-text-strong); +} + +#login-panel input::placeholder { + color: var(--color-text-muted); + opacity: 1; +} + #login-panel select { width: 100%; font-size: 14px; } #login-panel select option#eauth-default { - color: gray; + color: var(--color-text-muted); } .attribution { @@ -64,7 +77,7 @@ #notice { height: 0; overflow-y: hidden; - color: white; + color: var(--color-text-inverse); padding: 0; border-radius: 2px; text-align: center; diff --git a/saltgui/static/stylesheets/main.css b/saltgui/static/stylesheets/main.css index fccba7f1a..ee8e39ede 100644 --- a/saltgui/static/stylesheets/main.css +++ b/saltgui/static/stylesheets/main.css @@ -10,23 +10,25 @@ body { margin: 0; padding: 0; font-family: Roboto, Helvetica, Arial, sans-serif; + color: var(--color-text-primary); } @media not print { body { - background-color: #263238; + background-color: var(--color-background-page); } } header { - border-top: 2px solid #4caf50; - background-color: white; + border-top: 2px solid var(--color-border-accent); + background-color: var(--color-background-header); + box-shadow: var(--color-shadow-header); margin-top: 0; } .logo { cursor: pointer; - color: #4caf50; + color: var(--color-text-accent); font-size: 30px; font-weight: normal; display: inline-block; @@ -42,7 +44,7 @@ header { .docu { cursor: pointer; - color: gray; + color: var(--color-text-muted); font-size: 30px; font-weight: normal; display: inline-block; @@ -58,11 +60,11 @@ header { } .docu:hover { - color: #4caf50; + color: var(--color-text-accent); } h1 { - color: #4caf50; + color: var(--color-text-accent); font-weight: lighter; font-size: 20px; margin: 0 10px 0 0; @@ -70,12 +72,13 @@ h1 { } .msg { - color: #505050; + color: var(--color-text-primary); padding-top: 5px; } .panel { - background-color: white; + background-color: var(--color-background-panel); + color: var(--color-text-primary); padding: 20px; border-radius: 1px; @@ -114,11 +117,11 @@ h1 { display: inline-block; min-width: 50px; text-align: center; - background-color: #eee; + background-color: var(--color-background-control-muted); margin: 0; cursor: pointer; font-size: 18px; - color: #666; + color: var(--color-text-quiet); height: 24px; vertical-align: middle; padding-left: 10px; @@ -150,7 +153,7 @@ h1 { } .small-button:hover { - color: #4caf50; + color: var(--color-text-accent); } .small-button-for-hover { @@ -174,25 +177,30 @@ h1 { .search-error { display: block; - color: red; + color: var(--color-status-failure); margin-bottom: 10px; margin-left: 10px; } .menu-item { display: inline-block; + color: var(--color-text-default); font-size: 18px; font-weight: lighter; padding: 15px 30px; } +.menu-item-dimmed { + color: var(--color-menu-dimmed); +} + .menu-item-active { font-weight: bold; } .menu-item:hover { - background: rgba(0, 0, 0, 15%); - color: #4caf50; + background: var(--color-background-hover); + color: var(--color-text-accent); cursor: pointer; } @@ -203,7 +211,7 @@ h1 { } .menu-item-first-letter:hover::first-letter { - text-decoration: underline #4caf50 double; + text-decoration: underline var(--color-text-accent) double; text-decoration-skip-ink: none; } @@ -218,7 +226,8 @@ h1 { } #warning { - background: yellow; + background: var(--color-background-warning); + color: var(--color-text-warning); margin-top: 5px; padding: 5px 10px 5px 20px; } @@ -253,7 +262,7 @@ h1 { width: 100%; height: 100%; z-index: 2; - background-color: rgba(0, 0, 0, 86%); + background-color: var(--color-background-overlay); } .popup h1 { @@ -265,7 +274,8 @@ h1 { .run-command { padding: 30px; z-index: 3; - background-color: white; + background-color: var(--color-background-popup); + color: var(--color-text-primary); position: relative; top: 5px; margin-left: 15px; @@ -278,8 +288,8 @@ h1 { } pre.output { - background-color: #272727; - color: white; + background-color: var(--color-background-code); + color: var(--color-text-inverse); margin: 0; padding: 10px; border-radius: 2px; @@ -304,10 +314,10 @@ pre.output { } .warning-button:hover { - color: #4caf50; + color: var(--color-text-accent); cursor: pointer; } .state-details-compressed { - color: gray; + color: var(--color-text-muted); } diff --git a/saltgui/static/stylesheets/page.css b/saltgui/static/stylesheets/page.css index 492a50ecb..c4fe4ab98 100644 --- a/saltgui/static/stylesheets/page.css +++ b/saltgui/static/stylesheets/page.css @@ -20,7 +20,7 @@ pre a.disabled:hover { } pre.output a { - color: yellow; + color: var(--color-text-code-link); cursor: pointer; } @@ -44,16 +44,16 @@ pre.output div:first-of-type { pre .minion-id.host-success, pre #summary-jobs-active .host-success { - color: lime; + color: var(--color-status-success); } pre .minion-id.host-failure, pre #summary-jobs-active .host-failure { - color: red; + color: var(--color-status-failure); } td.address > span { - color: #3f51b5; + color: var(--color-text-link); cursor: copy; position: relative; } @@ -63,6 +63,11 @@ td.tasks span { padding-bottom: 3px; } +td.tasks span.task, +td.tasks span.tasksummary { + background-color: var(--color-background-code); +} + td.tasks span.task:first-child, td.tasks span.tasksummary { padding-left: 2px; @@ -77,11 +82,11 @@ pre .minion-id.host-skips, pre #summary-jobs-active .host-skips, pre .minion-id.host-no-response, pre #summary-jobs-active .host-no-response { - color: yellow; + color: var(--color-status-warning); } pre .minion-id { - color: #4caf50; + color: var(--color-text-accent); } .task-summary { @@ -90,7 +95,7 @@ pre .minion-id { } pre span.active { - color: greenyellow; + color: var(--color-status-success-soft); font-weight: bold; } @@ -101,7 +106,7 @@ table { } table tr th { - border-bottom: 3px double #4caf50; + border-bottom: 3px double var(--color-border-accent); padding-right: 20px; padding-top: 5px; padding-bottom: 5px; @@ -119,23 +124,23 @@ table tr td { } .no-job-status { - color: gray; + color: var(--color-text-muted); } .no-job-details { - color: gray; + color: var(--color-text-muted); } table thead th, table tbody td { padding: 8px; text-align: left; - border-bottom: 1px solid #ddd; - color: #505050; + border-bottom: 1px solid var(--color-border-default); + color: var(--color-text-primary); } table thead th { - border-bottom: 2px solid #ddd; + border-bottom: 2px solid var(--color-border-default); } table tr th:last-child { @@ -158,17 +163,24 @@ table tr td:last-of-type { opacity: 0.4; } +.button-in-text { + background-color: var(--color-background-control-muted); + border-radius: 2px; + color: var(--color-text-strong); + padding: 0 0.25em; +} + .menu-item-hidden { display: none; } .run-command-button { - color: #263238; + color: var(--color-text-strong); cursor: pointer; } .run-command-button:hover { - color: #2e7d32; + color: var(--color-text-accent); } #template-catmenu-here, @@ -181,25 +193,45 @@ table tr td:last-of-type { position: relative; } +.job-details-success, +.text-success { + color: var(--color-status-accepted); +} + +.job-details-warning, +.text-warning { + color: var(--color-status-caution); +} + +.job-details-failure, +.text-error { + color: var(--color-status-failure); +} + +.job-details-info, +.text-info { + color: var(--color-status-info); +} + .accepted { - color: #00a000; + color: var(--color-status-accepted); } .denied { - color: #f0f; + color: var(--color-status-denied); } .unaccepted, .keyunknown { - color: #f00; + color: var(--color-status-unaccepted); } .rejected { - color: #00f; + color: var(--color-status-rejected); } .offline { - color: red; + color: var(--color-status-offline); } .prefiximage { @@ -217,14 +249,14 @@ table tr td:last-of-type { .jobs td .target { font-weight: 500; font-size: 18px; - color: #505050; + color: var(--color-text-primary); white-space: nowrap; } .jobs td .function { font-weight: 500; font-size: 14px; - color: #3a3a3a; + color: var(--color-text-secondary); } .jobs td .time { @@ -250,7 +282,7 @@ table tr td:last-of-type { } .highlight-rows tbody tr:hover { - background-color: whitesmoke; + background-color: var(--color-background-row-hover); cursor: pointer; } @@ -279,21 +311,21 @@ table tr td:last-of-type { /* tasks */ .task-success { - color: lime; + color: var(--color-status-success); } .task-success-changes { - color: aqua; + color: var(--color-status-info); } .task-failure, .task-failure-changes { - color: red; + color: var(--color-status-failure); } .task-skipped, .task-skipped-changes { - color: yellow; + color: var(--color-status-warning); } pre .task-success, @@ -305,6 +337,17 @@ pre .task-failure-changes { cursor: pointer; } +.doc-inline-code { + background-color: var(--color-background-control-muted); + border-radius: 2px; + color: var(--color-text-primary); + padding: 0 0.25em; +} + +.doc-inline-highlight { + color: var(--color-text-code-link); +} + @media print { .no-print, .no-print * { diff --git a/saltgui/static/stylesheets/schedules.css b/saltgui/static/stylesheets/schedules.css index cfcbfdebb..56287d85e 100644 --- a/saltgui/static/stylesheets/schedules.css +++ b/saltgui/static/stylesheets/schedules.css @@ -7,5 +7,5 @@ td.schedule-value { } td.schedule-disabled { - color: gray; + color: var(--color-text-muted); } diff --git a/saltgui/static/stylesheets/theme.css b/saltgui/static/stylesheets/theme.css new file mode 100644 index 000000000..a9e4855e5 --- /dev/null +++ b/saltgui/static/stylesheets/theme.css @@ -0,0 +1,113 @@ +:root { + color-scheme: light; + + --color-background-page: #263238; + --color-background-header: #fff; + --color-background-panel: #fff; + --color-background-popup: #fff; + --color-background-overlay: rgba(0, 0, 0, 86%); + --color-background-code: #272727; + --color-background-control: #fff; + --color-background-control-muted: #eee; + --color-background-control-soft: #f9f9f9; + --color-background-hover: rgba(0, 0, 0, 15%); + --color-background-row-hover: whitesmoke; + --color-background-tooltip: rgba(76, 175, 80, 80%); + --color-background-tooltip-error: rgba(244, 67, 54, 92%); + --color-background-tooltip-hover: #e0e0e0; + --color-background-tooltip-hover-code: #484848; + --color-background-warning: #ffeb3b; + --color-background-highlight: rgba(128, 128, 128, 60%); + --color-text-default: #000; + --color-text-strong: #263238; + --color-text-primary: #505050; + --color-text-secondary: #3a3a3a; + --color-text-muted: gray; + --color-text-quiet: #666; + --color-text-accent: #4caf50; + --color-text-link: #3f51b5; + --color-text-inverse: #fff; + --color-text-code-link: #ffeb3b; + --color-text-tooltip: #fff; + --color-text-tooltip-error: #fff; + --color-text-warning: #263238; + --color-border-accent: #4caf50; + --color-border-default: #ddd; + --color-border-control: #e2e2e2; + --color-shadow-panel: 0 0 24px rgba(0, 0, 0, 70%); + --color-shadow-dropdown: 0 3px 10px -2px rgba(0, 0, 0, 30%); + --color-shadow-dropdown-large: 0 8px 16px 0 rgba(0, 0, 0, 20%); + --color-shadow-button: 0 0 5px rgba(33, 33, 33, 50%); + --color-shadow-header: none; + --color-menu-dimmed: lightgray; + --color-status-accepted: #00a000; + --color-status-denied: #f0f; + --color-status-unaccepted: #f00; + --color-status-rejected: #00f; + --color-status-offline: #f00; + --color-status-success: lime; + --color-status-success-soft: greenyellow; + --color-status-warning: yellow; + --color-status-caution: orange; + --color-status-failure: red; + --color-status-info: aqua; + --color-notice-muted: gray; + --color-notice-danger: #f44336; +} + +:root[data-theme="dark"] { + color-scheme: dark; + + --color-background-page: #071318; + --color-background-header: #0d1f26; + --color-background-panel: #122a33; + --color-background-popup: #122a33; + --color-background-overlay: rgba(0, 0, 0, 86%); + --color-background-code: #0b171d; + --color-background-control: #173540; + --color-background-control-muted: #173540; + --color-background-control-soft: #173540; + --color-background-hover: rgba(34, 199, 189, 12%); + --color-background-row-hover: rgba(34, 199, 189, 8%); + --color-background-tooltip: rgba(34, 199, 189, 82%); + --color-background-tooltip-error: rgba(255, 127, 138, 90%); + --color-background-tooltip-hover: rgba(34, 199, 189, 12%); + --color-background-tooltip-hover-code: rgba(34, 199, 189, 12%); + --color-background-warning: rgba(244, 193, 79, 18%); + --color-background-highlight: rgba(11, 23, 29, 90%); + --color-text-default: #e6f7f5; + --color-text-strong: #e6f7f5; + --color-text-primary: #c4d7d5; + --color-text-secondary: #9ab8b5; + --color-text-muted: #9ab8b5; + --color-text-quiet: #9ab8b5; + --color-text-accent: #22c7bd; + --color-text-link: #8fc4ff; + --color-text-inverse: #e6f7f5; + --color-text-code-link: #ffe38f; + --color-text-tooltip: #062126; + --color-text-tooltip-error: #24050a; + --color-text-warning: #ffe3a2; + --color-border-accent: rgba(34, 199, 189, 12%); + --color-border-default: rgba(154, 184, 181, 18%); + --color-border-control: rgba(154, 184, 181, 18%); + --color-shadow-panel: 0 18px 40px rgba(0, 0, 0, 38%); + --color-shadow-dropdown: 0 18px 40px rgba(0, 0, 0, 38%); + --color-shadow-dropdown-large: 0 18px 40px rgba(0, 0, 0, 38%); + --color-shadow-button: 0 10px 30px rgba(34, 199, 189, 24%); + --color-shadow-header: 0 10px 30px rgba(0, 0, 0, 18%); + --color-menu-dimmed: #85a3a0; + --color-status-accepted: #60f29d; + --color-status-denied: #ff8df7; + --color-status-unaccepted: #ff8e8e; + --color-status-rejected: #7aa7ff; + --color-status-offline: #ff8e8e; + --color-status-success: #78ffaf; + --color-status-success-soft: #bfff72; + --color-status-warning: #ffe38f; + --color-status-caution: #ffb26b; + --color-status-failure: #ff8e8e; + --color-status-info: #66e7ff; + --color-notice-muted: #5a7980; + --color-notice-danger: #b93a48; +} diff --git a/saltgui/static/stylesheets/tooltip.css b/saltgui/static/stylesheets/tooltip.css index faf2944b1..0da1b1ab0 100644 --- a/saltgui/static/stylesheets/tooltip.css +++ b/saltgui/static/stylesheets/tooltip.css @@ -5,8 +5,8 @@ .tooltip > .tooltip-text { display: none; font-size: 14px; - background-color: rgba(76, 175, 80, 80%); /* #4caf50 */ - color: white; + background-color: var(--color-background-tooltip); + color: var(--color-text-tooltip); padding: 7px; border-radius: 3px; position: absolute; @@ -36,7 +36,8 @@ .tooltip > .tooltip-text-error-bottom-left { text-align: left; transform: translate(-5%, 0); - background-color: red; + background-color: var(--color-background-tooltip-error); + color: var(--color-text-tooltip-error); font-weight: bold; } @@ -56,8 +57,7 @@ } .tooltip:hover { - /* only slightly darker than 'whitesmoke(#f5f5f5)' */ - background-color: #e0e0e0; + background-color: var(--color-background-tooltip-hover); } .tooltip:hover > .tooltip-text { @@ -65,12 +65,11 @@ } pre.output .tooltip > .tooltip-text { - background-color: rgba(76, 175, 80, 80%); /* #4caf50 */ + background-color: var(--color-background-tooltip); } pre.output .tooltip:hover { - /* only slightly lighter than '#272727' */ - background-color: #484848; + background-color: var(--color-background-tooltip-hover-code); } /* The arrow/triangle of the tooltip */ @@ -83,7 +82,7 @@ pre.output .tooltip:hover { border-width: 5px; border-style: solid; top: 100%; - border-color: rgba(76, 175, 80, 80%) transparent transparent transparent; + border-color: var(--color-background-tooltip) transparent transparent transparent; } .tooltip > .tooltip-text-logo { @@ -108,7 +107,7 @@ pre.output .tooltip:hover { .tooltip > .tooltip-text-error-bottom-left::after { left: calc(5% - 2.5px); - border-color: red transparent transparent; + border-color: var(--color-background-tooltip-error) transparent transparent; } .tooltip > .tooltip-text-bottom-center::after { @@ -120,5 +119,5 @@ pre.output .tooltip:hover { } pre.output .tooltip > .tooltip-text::after { - border-color: rgba(76, 175, 80, 80%) transparent transparent transparent; /* #4caf50 */ + border-color: var(--color-background-tooltip) transparent transparent transparent; } diff --git a/tests/unit/Output.test.js b/tests/unit/Output.test.js index 9112d5598..41bc23ad0 100644 --- a/tests/unit/Output.test.js +++ b/tests/unit/Output.test.js @@ -433,7 +433,7 @@ describe("Unittests for Output.js", () => { OutputDocumentation.addDocumentationOutput(container, output); assert.isTrue( container.innerHTML.includes( - "systemd-run(1)")); + "systemd-run(1)")); done(); });