diff --git a/web/src/admin/AdminInterface/AdminInterface.ts b/web/src/admin/AdminInterface/AdminInterface.ts
index 834c98f37..7b780506e 100644
--- a/web/src/admin/AdminInterface/AdminInterface.ts
+++ b/web/src/admin/AdminInterface/AdminInterface.ts
@@ -17,7 +17,6 @@ import "@goauthentik/elements/notifications/NotificationDrawer";
import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch";
import "@goauthentik/elements/router/RouterOutlet";
import "@goauthentik/elements/sidebar/Sidebar";
-import "@goauthentik/elements/sidebar/SidebarItem";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
diff --git a/web/src/admin/AdminInterface/AdminSidebar.ts b/web/src/admin/AdminInterface/AdminSidebar.ts
index 2f973ca7e..991d5fa66 100644
--- a/web/src/admin/AdminInterface/AdminSidebar.ts
+++ b/web/src/admin/AdminInterface/AdminSidebar.ts
@@ -4,18 +4,40 @@ import { me } from "@goauthentik/common/users";
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
import { AKElement } from "@goauthentik/elements/Base";
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
+import "@goauthentik/elements/sidebar/Sidebar";
+import { SidebarAttributes, SidebarEntry, SidebarEventHandler } from "@goauthentik/elements/sidebar/SidebarItems";
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
-import { spread } from "@open-wc/lit-helpers";
import { consume } from "@lit-labs/context";
import { msg, str } from "@lit/localize";
-import { TemplateResult, html, nothing } from "lit";
+import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
-import { map } from "lit/directives/map.js";
-import { AdminApi, CapabilitiesEnum, CoreApi, UiThemeEnum, Version } from "@goauthentik/api";
+import { ProvidersApi, TypeCreate } from "@goauthentik/api";
+import { AdminApi, CapabilitiesEnum, CoreApi, Version } from "@goauthentik/api";
import type { Config, SessionUser, UserSelf } from "@goauthentik/api";
+/**
+ * AdminSidebar
+ *
+ * Encapsulates the logic for the administration sidebar: what to show and, initially, when to show
+ * it. Rendering decisions are left to the sidebar itself.
+ */
+
+type LocalSidebarEntry = [
+ string | SidebarEventHandler | null,
+ string,
+ (SidebarAttributes | string[] | null)?, // eslint-disable-line
+ LocalSidebarEntry[]?,
+];
+
+const localToSidebarEntry = (l: LocalSidebarEntry): SidebarEntry => ({
+ path: l[0],
+ label: l[1],
+ ...(l[2]? { attributes: Array.isArray(l[2]) ? { activeWhen: l[2] } : l[2] } : {}),
+ ...(l[3] ? { children: l[3].map(localToSidebarEntry) } : {}),
+});
+
@customElement("ak-admin-sidebar")
export class AkAdminSidebar extends AKElement {
@property({ type: Boolean, reflect: true })
@@ -27,6 +49,9 @@ export class AkAdminSidebar extends AKElement {
@state()
impersonation: UserSelf["username"] | null = null;
+ @state()
+ providerTypes: TypeCreate[] = [];
+
@consume({ context: authentikConfigContext })
public config!: Config;
@@ -38,6 +63,9 @@ export class AkAdminSidebar extends AKElement {
me().then((user: SessionUser) => {
this.impersonation = user.original ? user.user.username : null;
});
+ new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => {
+ this.providerTypes = types;
+ });
this.toggleOpen = this.toggleOpen.bind(this);
this.checkWidth = this.checkWidth.bind(this);
}
@@ -51,9 +79,7 @@ export class AkAdminSidebar extends AKElement {
checkWidth() {
// This works just fine, but it assumes that the `--ak-sidebar--minimum-auto-width` is in
// REMs. If that changes, this code will have to be adjusted as well.
- const minWidth =
- parseFloat(getRootStyle("--ak-sidebar--minimum-auto-width")) *
- parseFloat(getRootStyle("font-size"));
+ const minWidth = parseFloat(getRootStyle("--ak-sidebar--minimum-auto-width")) * parseFloat(getRootStyle("font-size"));
this.open = window.innerWidth >= minWidth;
}
@@ -75,19 +101,6 @@ export class AkAdminSidebar extends AKElement {
super.disconnectedCallback();
}
- render() {
- return html`
-
- `;
- }
-
updated() {
// This is permissible as`:host.classList` is not one of the properties Lit uses as a
// scheduling trigger. This sort of shenanigans can trigger an loop, in that it will trigger
@@ -98,26 +111,43 @@ export class AkAdminSidebar extends AKElement {
this.classList.add(this.open ? "pf-m-expanded" : "pf-m-collapsed");
}
- renderSidebarItems(): TemplateResult {
- // The second attribute type is of string[] to help with the 'activeWhen' control, which was
- // commonplace and singular enough to merit its own handler.
- type SidebarEntry = [
- path: string | null,
- label: string,
- attributes?: Record | string[] | null, // eslint-disable-line
- children?: SidebarEntry[],
- ];
+ get sidebarItems(): SidebarEntry[] {
+ const reload = () =>
+ new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => {
+ window.location.reload();
+ });
// prettier-ignore
- const sidebarContent: SidebarEntry[] = [
- ["/if/user/", msg("User interface"), { "?isAbsoluteLink": true, "?highlight": true }],
- [null, msg("Dashboards"), { "?expanded": true }, [
+ const newVersionMessage: LocalSidebarEntry[] = this.version && this.version !== VERSION
+ ? [["https://goauthentik.io", msg("A newer version of the frontend is available."), { "?highlight": true }]]
+ : [];
+
+ // prettier-ignore
+ const impersonationMessage: LocalSidebarEntry[] = this.impersonation
+ ? [[reload, msg(str`You're currently impersonating ${this.impersonation}. Click to stop.`)]]
+ : [];
+
+ // prettier-ignore
+ const enterpriseMenu: LocalSidebarEntry[] = this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise)
+ ? [[null, msg("Enterprise"), null, [["/enterprise/licenses", msg("Licenses")]]]]
+ : [];
+
+ // prettier-ignore
+ const providerTypes: LocalSidebarEntry[] = this.providerTypes.map((ptype) =>
+ ([`/core/providers;${encodeURIComponent(JSON.stringify({ search: ptype.modelName.replace(/provider$/, "") }))}`, ptype.name]));
+
+ // prettier-ignore
+ const localSidebar: LocalSidebarEntry[] = [
+ ...(newVersionMessage),
+ ...(impersonationMessage),
+ ["/if/user/", msg("User interface"), { isAbsoluteLink: true, highlight: true }],
+ [null, msg("Dashboards"), { expanded: true }, [
["/administration/overview", msg("Overview")],
["/administration/dashboard/users", msg("User Statistics")],
["/administration/system-tasks", msg("System Tasks")]]],
[null, msg("Applications"), null, [
["/core/applications", msg("Applications"), [`^/core/applications/(?${SLUG_REGEX})$`]],
- ["/core/providers", msg("Providers"), [`^/core/providers/(?${ID_REGEX})$`]],
+ ["/core/providers", msg("Providers"), [`^/core/providers/(?${ID_REGEX})$`], providerTypes],
["/outpost/outposts", msg("Outposts")]]],
[null, msg("Events"), null, [
["/events/log", msg("Logs"), [`^/events/log/(?${UUID_REGEX})$`]],
@@ -142,73 +172,14 @@ export class AkAdminSidebar extends AKElement {
[null, msg("System"), null, [
["/core/tenants", msg("Tenants")],
["/crypto/certificates", msg("Certificates")],
- ["/outpost/integrations", msg("Outpost Integrations")]]]
+ ["/outpost/integrations", msg("Outpost Integrations")]]],
+ ...(enterpriseMenu)
];
- // Typescript requires the type here to correctly type the recursive path
- type SidebarRenderer = (_: SidebarEntry) => TemplateResult;
-
- const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => {
- const properties = Array.isArray(attributes)
- ? { ".activeWhen": attributes }
- : attributes ?? {};
- if (path) {
- properties["path"] = path;
- }
- return html`
- ${label ? html`${label}` : nothing}
- ${map(children, renderOneSidebarItem)}
- `;
- };
-
- // prettier-ignore
- return html`
- ${this.renderNewVersionMessage()}
- ${this.renderImpersonationMessage()}
- ${map(sidebarContent, renderOneSidebarItem)}
- ${this.renderEnterpriseMessage()}
- `;
+ return localSidebar.map(localToSidebarEntry);
}
- renderNewVersionMessage() {
- return this.version && this.version !== VERSION
- ? html`
-
- ${msg("A newer version of the frontend is available.")}
-
- `
- : nothing;
- }
-
- renderImpersonationMessage() {
- const reload = () =>
- new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => {
- window.location.reload();
- });
-
- return this.impersonation
- ? html`
- ${msg(
- str`You're currently impersonating ${this.impersonation}. Click to stop.`,
- )}
- `
- : nothing;
- }
-
- renderEnterpriseMessage() {
- return this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise)
- ? html`
-
- ${msg("Enterprise")}
-
- ${msg("Licenses")}
-
-
- `
- : nothing;
+ render() {
+ return html` `;
}
}
diff --git a/web/src/elements/sidebar/Sidebar.ts b/web/src/elements/sidebar/Sidebar.ts
index 69604b13f..732375560 100644
--- a/web/src/elements/sidebar/Sidebar.ts
+++ b/web/src/elements/sidebar/Sidebar.ts
@@ -1,9 +1,10 @@
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/sidebar/SidebarBrand";
import "@goauthentik/elements/sidebar/SidebarUser";
+import "@goauthentik/elements/sidebar/SidebarItems";
import { CSSResult, TemplateResult, css, html } from "lit";
-import { customElement } from "lit/decorators.js";
+import { customElement, property } from "lit/decorators.js";
import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
@@ -11,8 +12,13 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { UiThemeEnum } from "@goauthentik/api";
+import type { SidebarEntry } from "./SidebarItems";
+
@customElement("ak-sidebar")
export class Sidebar extends AKElement {
+ @property({ type: Array })
+ entries: SidebarEntry[] = [];
+
static get styles(): CSSResult[] {
return [
PFBase,
@@ -45,7 +51,8 @@ export class Sidebar extends AKElement {
height: 100%;
overflow-y: hidden;
}
- .pf-c-nav__list {
+
+ ak-sidebar-items {
flex-grow: 1;
overflow-y: auto;
}
@@ -66,14 +73,10 @@ export class Sidebar extends AKElement {
}
render(): TemplateResult {
- return html`