web: add command palette

Adds [Ninja Keys](https://github.com/ssleptsov/ninja-keys) (MIT License) to the Admin Interface.
This is a trivial demo. Start the UI and begin by pressing CMD-K or CTRL-K in the Admin Interface.

- Because a lot of the Create and Update operations are accessible as activations-on-modals rather
  than navigable components in their own right, we don't have a way to encode commands like "Start
  the Application Wizard" or "Add a new Policy."
- Our search isn't very quick, so "Edit Application Foo" would have intolerable delays while we
  looked up a list of applications that could be edited.
- The `icon` feature is relatively difficult to exploit because it's a web component and its inner
  styles are inacessible.
- There seem to be cases where the modal-popup and charts conflict.

If we want to move forward with this, we have the challenge of finding activation means for the
various Create and Edit features, and whether or not Search can be meaningfully integrated into the
results.

It would also be nifty if the User and Admin palettes treated each other as peers; you could just go
"CMD-K My User Page" and it would just *take* you there, and "CMD-K Admin Applications" to go back,
creating an illusion of seamlessness.
This commit is contained in:
Ken Sternberg
2024-03-15 15:21:51 -07:00
parent 8b4e0361c4
commit 16b3ca3715
4 changed files with 243 additions and 6 deletions

61
web/package-lock.json generated
View File

@ -39,6 +39,7 @@
"lit": "^3.1.2", "lit": "^3.1.2",
"md-front-matter": "^1.0.4", "md-front-matter": "^1.0.4",
"mermaid": "^10.9.0", "mermaid": "^10.9.0",
"ninja-keys": "^1.2.2",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"style-mod": "^4.1.2", "style-mod": "^4.1.2",
@ -3326,6 +3327,33 @@
"@lit/reactive-element": "^1.0.0 || ^2.0.0" "@lit/reactive-element": "^1.0.0 || ^2.0.0"
} }
}, },
"node_modules/@material/mwc-icon": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-icon/-/mwc-icon-0.25.3.tgz",
"integrity": "sha512-36076AWZIRSr8qYOLjuDDkxej/HA0XAosrj7TS1ZeLlUBnLUtbDtvc1S7KSa0hqez7ouzOqGaWK24yoNnTa2OA==",
"dependencies": {
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-icon/node_modules/@lit/reactive-element": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"node_modules/@material/mwc-icon/node_modules/lit": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
"dependencies": {
"@lit/reactive-element": "^1.6.0",
"lit-element": "^3.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/@mdx-js/react": { "node_modules/@mdx-js/react": {
"version": "2.3.0", "version": "2.3.0",
"dev": true, "dev": true,
@ -11381,6 +11409,11 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/hotkeys-js": {
"version": "3.8.7",
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.8.7.tgz",
"integrity": "sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg=="
},
"node_modules/http-errors": { "node_modules/http-errors": {
"version": "2.0.0", "version": "2.0.0",
"dev": true, "dev": true,
@ -13640,6 +13673,34 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ninja-keys": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/ninja-keys/-/ninja-keys-1.2.2.tgz",
"integrity": "sha512-ylo8jzKowi3XBHkgHRjBJaKQkl32WRLr7kRiA0ajiku11vHRDJ2xANtTScR5C7XlDwKEOYvUPesCKacUeeLAYw==",
"dependencies": {
"@material/mwc-icon": "0.25.3",
"hotkeys-js": "3.8.7",
"lit": "2.2.6"
}
},
"node_modules/ninja-keys/node_modules/@lit/reactive-element": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"node_modules/ninja-keys/node_modules/lit": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.2.6.tgz",
"integrity": "sha512-K2vkeGABfSJSfkhqHy86ujchJs3NR9nW1bEEiV+bXDkbiQ60Tv5GUausYN2mXigZn8lC1qXuc46ArQRKYmumZw==",
"dependencies": {
"@lit/reactive-element": "^1.3.0",
"lit-element": "^3.2.0",
"lit-html": "^2.2.0"
}
},
"node_modules/node-abi": { "node_modules/node-abi": {
"version": "3.56.0", "version": "3.56.0",
"license": "MIT", "license": "MIT",

View File

@ -14,8 +14,8 @@
"build": "run-s build-locales esbuild:build", "build": "run-s build-locales esbuild:build",
"build-proxy": "run-s build-locales esbuild:build-proxy", "build-proxy": "run-s build-locales esbuild:build-proxy",
"watch": "run-s build-locales esbuild:watch", "watch": "run-s build-locales esbuild:watch",
"lint": "eslint . --max-warnings 0 --fix", "lint": "cross-env NODE_OPTIONS='--max_old_space_size=8192' eslint . --max-warnings 0 --fix",
"lint:precommit": "node scripts/eslint-precommit.mjs", "lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=8192' node scripts/eslint-precommit.mjs",
"lint:spelling": "node scripts/check-spelling.mjs", "lint:spelling": "node scripts/check-spelling.mjs",
"lit-analyse": "lit-analyzer src", "lit-analyse": "lit-analyzer src",
"precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier", "precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier",
@ -40,9 +40,9 @@
"@fortawesome/fontawesome-free": "^6.5.1", "@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2024.2.2-1709583949", "@goauthentik/api": "^2024.2.2-1709583949",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/reactive-element": "^2.0.4",
"@lit/context": "^1.1.0", "@lit/context": "^1.1.0",
"@lit/localize": "^0.12.1", "@lit/localize": "^0.12.1",
"@lit/reactive-element": "^2.0.4",
"@open-wc/lit-helpers": "^0.7.0", "@open-wc/lit-helpers": "^0.7.0",
"@patternfly/elements": "^2.4.0", "@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
@ -60,6 +60,7 @@
"lit": "^3.1.2", "lit": "^3.1.2",
"md-front-matter": "^1.0.4", "md-front-matter": "^1.0.4",
"mermaid": "^10.9.0", "mermaid": "^10.9.0",
"ninja-keys": "^1.2.2",
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"style-mod": "^4.1.2", "style-mod": "^4.1.2",
@ -79,6 +80,7 @@
"@hcaptcha/types": "^1.0.3", "@hcaptcha/types": "^1.0.3",
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.7.2", "@lit/localize-tools": "^0.7.2",
"@rollup/plugin-replace": "^5.0.5",
"@spotlightjs/spotlight": "^1.2.12", "@spotlightjs/spotlight": "^1.2.12",
"@storybook/addon-essentials": "^7.6.17", "@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.17", "@storybook/addon-links": "^7.6.17",
@ -95,9 +97,6 @@
"@types/showdown": "^2.0.6", "@types/showdown": "^2.0.6",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^7.2.0",
"@rollup/plugin-replace": "^5.0.5",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3", "babel-plugin-tsconfig-paths": "^1.0.3",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
@ -117,6 +116,8 @@
"pseudolocale": "^2.0.0", "pseudolocale": "^2.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^7.6.17", "storybook": "^7.6.17",
"storybook-addon-mock": "^4.3.0", "storybook-addon-mock": "^4.3.0",
"ts-lit-plugin": "^2.0.2", "ts-lit-plugin": "^2.0.2",

View File

@ -0,0 +1,172 @@
import { navigate } from "@goauthentik/elements/router/RouterOutlet";
import { INinjaAction } from "ninja-keys/dist/interfaces/ininja-action.js";
import { msg } from "@lit/localize";
export const adminCommands: INinjaAction[] = [
{
id: msg("Overview"),
title: msg("Dashboard"),
handler: () => navigate("/administration/overview"),
section: msg("Dashboards"),
},
{
handler: () => navigate("/administration/dashboard/users"),
id: msg("User Statistics"),
title: msg("User Statistics"),
icon: '<i class="pf-icon pf-icon-user"></i>',
section: msg("Dashboards"),
},
{
handler: () => navigate("/administration/system-tasks"),
id: msg("System Tasks"),
title: msg("System Tasks"),
section: msg("Dashboards"),
},
{
handler: () => navigate("/core/applications"),
id: msg("Applications"),
title: msg("Applications"),
section: msg("Applications"),
},
{
handler: () => navigate("/core/providers"),
id: msg("Providers"),
title: msg("Providers"),
section: msg("Applications"),
},
{
handler: () => navigate("/outpost/outposts"),
id: msg("Outposts"),
title: msg("Outposts"),
section: msg("Applications"),
},
{
handler: () => navigate("/events/log"),
id: msg("Logs"),
title: msg("Logs"),
section: msg("Events"),
},
{
handler: () => navigate("/events/rules"),
id: msg("Notification Rules"),
title: msg("Notification Rules"),
section: msg("Events"),
},
{
handler: () => navigate("/events/transports"),
id: msg("Notification Transports"),
title: msg("Notification Transports"),
section: msg("Events"),
},
{
handler: () => navigate("/policy/policies"),
id: msg("Policies"),
title: msg("Policies"),
section: msg("Customization"),
},
{
handler: () => navigate("/core/property-mappings"),
id: msg("Property Mappings"),
title: msg("Property Mappings"),
section: msg("Customization"),
},
{
handler: () => navigate("/blueprints/instances"),
id: msg("Blueprints"),
title: msg("Blueprints"),
section: msg("Customization"),
},
{
handler: () => navigate("/policy/reputation"),
id: msg("Reputation scores"),
title: msg("Reputation scores"),
section: msg("Customization"),
},
{
handler: () => navigate("/flow/flows"),
id: msg("Flows"),
title: msg("Flows"),
section: msg("Flows"),
},
{
handler: () => navigate("/flow/stages"),
id: msg("Stages"),
title: msg("Stages"),
section: msg("Flows"),
},
{
handler: () => navigate("/flow/stages/prompts"),
id: msg("Prompts"),
title: msg("Prompts"),
section: msg("Flows"),
},
{
handler: () => navigate("/identity/users"),
id: msg("Users"),
title: msg("Users"),
section: msg("Directory"),
},
{
handler: () => navigate("/identity/groups"),
id: msg("Groups"),
title: msg("Groups"),
section: msg("Directory"),
},
{
handler: () => navigate("/identity/roles"),
id: msg("Roles"),
title: msg("Roles"),
section: msg("Directory"),
},
{
handler: () => navigate("/core/sources"),
id: msg("Federation and Social login"),
title: msg("Federation and Social login"),
section: msg("Directory"),
},
{
handler: () => navigate("/core/tokens"),
id: msg("Tokens and App passwords"),
title: msg("Tokens and App passwords"),
section: msg("Directory"),
},
{
handler: () => navigate("/flow/stages/invitations"),
id: msg("Invitations"),
title: msg("Invitations"),
section: msg("Directory"),
},
{
handler: () => navigate("/core/brands"),
id: msg("Brands"),
title: msg("Brands"),
section: msg("System"),
},
{
handler: () => navigate("/crypto/certificates"),
id: msg("Certificates"),
title: msg("Certificates"),
section: msg("System"),
},
{
handler: () => navigate("/outpost/integrations"),
id: msg("Outpost Integrations"),
title: msg("Outpost Integrations"),
section: msg("System"),
},
{
handler: () => navigate("/admin/settings"),
id: msg("Settings"),
title: msg("Settings"),
section: msg("System"),
},
{
handler: () => window.location.assign("/if/user/"),
id: msg("User interface"),
title: msg("Go to my User page"),
},
];

View File

@ -18,6 +18,7 @@ import { getURLParam, updateURLParams } from "@goauthentik/elements/router/Route
import "@goauthentik/elements/router/RouterOutlet"; import "@goauthentik/elements/router/RouterOutlet";
import "@goauthentik/elements/sidebar/Sidebar"; import "@goauthentik/elements/sidebar/Sidebar";
import "@goauthentik/elements/sidebar/SidebarItem"; import "@goauthentik/elements/sidebar/SidebarItem";
import "ninja-keys";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
@ -30,6 +31,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { AdminApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api"; import { AdminApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api";
import { adminCommands } from "./AdminCommands";
import "./AdminSidebar"; import "./AdminSidebar";
@customElement("ak-interface-admin") @customElement("ak-interface-admin")
@ -117,6 +119,7 @@ export class AdminInterface extends EnterpriseAwareInterface {
}; };
return html` <ak-locale-context> return html` <ak-locale-context>
<ninja-keys .data=${adminCommands} noAutoLoadMdicons></ninja-keys>
<div class="pf-c-page"> <div class="pf-c-page">
<ak-admin-sidebar <ak-admin-sidebar
class="pf-c-page__sidebar ${classMap(sidebarClasses)}" class="pf-c-page__sidebar ${classMap(sidebarClasses)}"