Compare commits
1 Commits
safari-adm
...
fix-shared
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f98c21f42 |
@ -4,17 +4,13 @@ import { ROUTES } from "@goauthentik/admin/Routes";
|
|||||||
import {
|
import {
|
||||||
EVENT_API_DRAWER_TOGGLE,
|
EVENT_API_DRAWER_TOGGLE,
|
||||||
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
EVENT_NOTIFICATION_DRAWER_TOGGLE,
|
||||||
EVENT_SIDEBAR_TOGGLE,
|
|
||||||
} from "@goauthentik/common/constants";
|
} from "@goauthentik/common/constants";
|
||||||
import { configureSentry } from "@goauthentik/common/sentry";
|
import { configureSentry } from "@goauthentik/common/sentry";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||||
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
|
import { AuthenticatedInterface } from "@goauthentik/elements/Interface";
|
||||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
|
|
||||||
import "@goauthentik/elements/ak-locale-context";
|
import "@goauthentik/elements/ak-locale-context";
|
||||||
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
||||||
import "@goauthentik/elements/banner/EnterpriseStatusBanner";
|
|
||||||
import "@goauthentik/elements/banner/VersionBanner";
|
|
||||||
import "@goauthentik/elements/banner/VersionBanner";
|
import "@goauthentik/elements/banner/VersionBanner";
|
||||||
import "@goauthentik/elements/messages/MessageContainer";
|
import "@goauthentik/elements/messages/MessageContainer";
|
||||||
import "@goauthentik/elements/messages/MessageContainer";
|
import "@goauthentik/elements/messages/MessageContainer";
|
||||||
@ -25,32 +21,25 @@ 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 { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators.js";
|
import { customElement, property, query, state } from "lit/decorators.js";
|
||||||
import { classMap } from "lit/directives/class-map.js";
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
|
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
||||||
import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
|
|
||||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
import { LicenseSummaryStatusEnum, SessionUser, UiThemeEnum } from "@goauthentik/api";
|
import { SessionUser, UiThemeEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
import {
|
import "./AdminSidebar";
|
||||||
AdminSidebarEnterpriseEntries,
|
|
||||||
AdminSidebarEntries,
|
|
||||||
renderSidebarItems,
|
|
||||||
} from "./AdminSidebar.js";
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
await import("@goauthentik/esbuild-plugin-live-reload/client");
|
await import("@goauthentik/esbuild-plugin-live-reload/client");
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ak-interface-admin")
|
@customElement("ak-interface-admin")
|
||||||
export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
export class AdminInterface extends AuthenticatedInterface {
|
||||||
//#region Properties
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
||||||
|
|
||||||
@ -65,24 +54,12 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
@query("ak-about-modal")
|
@query("ak-about-modal")
|
||||||
aboutModal?: AboutModal;
|
aboutModal?: AboutModal;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true })
|
|
||||||
public sidebarOpen = true;
|
|
||||||
|
|
||||||
#toggleSidebar = () => {
|
|
||||||
this.sidebarOpen = !this.sidebarOpen;
|
|
||||||
};
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Styles
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
PFPage,
|
PFPage,
|
||||||
PFButton,
|
PFButton,
|
||||||
PFDrawer,
|
PFDrawer,
|
||||||
PFNav,
|
|
||||||
css`
|
css`
|
||||||
.pf-c-page__main,
|
.pf-c-page__main,
|
||||||
.pf-c-drawer__content,
|
.pf-c-drawer__content,
|
||||||
@ -90,30 +67,23 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
z-index: auto !important;
|
z-index: auto !important;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-none {
|
.display-none {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-page {
|
.pf-c-page {
|
||||||
background-color: var(--pf-c-page--BackgroundColor) !important;
|
background-color: var(--pf-c-page--BackgroundColor) !important;
|
||||||
}
|
}
|
||||||
|
/* Global page background colour */
|
||||||
:host([theme="dark"]) {
|
:host([theme="dark"]) .pf-c-page {
|
||||||
/* Global page background colour */
|
--pf-c-page--BackgroundColor: var(--ak-dark-background);
|
||||||
.pf-c-page {
|
|
||||||
--pf-c-page--BackgroundColor: var(--ak-dark-background);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ak-enterprise-status,
|
||||||
ak-page-navbar {
|
ak-version-banner {
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
}
|
}
|
||||||
|
ak-admin-sidebar {
|
||||||
.ak-sidebar {
|
|
||||||
grid-area: nav;
|
grid-area: nav;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-drawer__panel {
|
.pf-c-drawer__panel {
|
||||||
z-index: var(--pf-global--ZIndex--xl);
|
z-index: var(--pf-global--ZIndex--xl);
|
||||||
}
|
}
|
||||||
@ -121,19 +91,9 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Lifecycle
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.ws = new WebsocketClient();
|
this.ws = new WebsocketClient();
|
||||||
}
|
|
||||||
|
|
||||||
public connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
|
|
||||||
window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
|
|
||||||
|
|
||||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
||||||
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
||||||
@ -150,11 +110,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.#toggleSidebar);
|
|
||||||
}
|
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
async firstUpdated(): Promise<void> {
|
||||||
configureSentry(true);
|
configureSentry(true);
|
||||||
this.user = await me();
|
this.user = await me();
|
||||||
@ -163,7 +118,6 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
this.user.user.isSuperuser ||
|
this.user.user.isSuperuser ||
|
||||||
// TODO: somehow add `access_admin_interface` to the API schema
|
// TODO: somehow add `access_admin_interface` to the API schema
|
||||||
this.user.user.systemPermissions.includes("access_admin_interface");
|
this.user.user.systemPermissions.includes("access_admin_interface");
|
||||||
|
|
||||||
if (!canAccessAdmin && this.user.user.pk > 0) {
|
if (!canAccessAdmin && this.user.user.pk > 0) {
|
||||||
window.location.assign("/if/user/");
|
window.location.assign("/if/user/");
|
||||||
}
|
}
|
||||||
@ -171,14 +125,10 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
const sidebarClasses = {
|
const sidebarClasses = {
|
||||||
"pf-c-page__sidebar": true,
|
|
||||||
"pf-m-light": this.activeTheme === UiThemeEnum.Light,
|
"pf-m-light": this.activeTheme === UiThemeEnum.Light,
|
||||||
"pf-m-expanded": this.sidebarOpen,
|
|
||||||
"pf-m-collapsed": !this.sidebarOpen,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen;
|
const drawerOpen = this.notificationDrawerOpen || this.apiDrawerOpen;
|
||||||
|
|
||||||
const drawerClasses = {
|
const drawerClasses = {
|
||||||
"pf-m-expanded": drawerOpen,
|
"pf-m-expanded": drawerOpen,
|
||||||
"pf-m-collapsed": !drawerOpen,
|
"pf-m-collapsed": !drawerOpen,
|
||||||
@ -186,18 +136,11 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) {
|
|||||||
|
|
||||||
return html` <ak-locale-context>
|
return html` <ak-locale-context>
|
||||||
<div class="pf-c-page">
|
<div class="pf-c-page">
|
||||||
<ak-page-navbar>
|
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||||
<ak-version-banner></ak-version-banner>
|
<ak-version-banner></ak-version-banner>
|
||||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
<ak-admin-sidebar
|
||||||
</ak-page-navbar>
|
class="pf-c-page__sidebar ${classMap(sidebarClasses)}"
|
||||||
|
></ak-admin-sidebar>
|
||||||
<ak-sidebar class="${classMap(sidebarClasses)}">
|
|
||||||
${renderSidebarItems(AdminSidebarEntries)}
|
|
||||||
${this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed
|
|
||||||
? renderSidebarItems(AdminSidebarEnterpriseEntries)
|
|
||||||
: nothing}
|
|
||||||
</ak-sidebar>
|
|
||||||
|
|
||||||
<div class="pf-c-page__drawer">
|
<div class="pf-c-page__drawer">
|
||||||
<div class="pf-c-drawer ${classMap(drawerClasses)}">
|
<div class="pf-c-drawer ${classMap(drawerClasses)}">
|
||||||
<div class="pf-c-drawer__main">
|
<div class="pf-c-drawer__main">
|
||||||
|
|||||||
@ -1,98 +1,186 @@
|
|||||||
|
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
|
||||||
|
import { me } from "@goauthentik/common/users";
|
||||||
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
|
import {
|
||||||
|
CapabilitiesEnum,
|
||||||
|
WithCapabilitiesConfig,
|
||||||
|
} from "@goauthentik/elements/Interface/capabilitiesProvider";
|
||||||
|
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
|
||||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
||||||
|
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
|
||||||
import { spread } from "@open-wc/lit-helpers";
|
import { spread } from "@open-wc/lit-helpers";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, html, nothing } from "lit";
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
import { repeat } from "lit/directives/repeat.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
import { map } from "lit/directives/map.js";
|
||||||
|
|
||||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
import { UiThemeEnum } from "@goauthentik/api";
|
||||||
// commonplace and singular enough to merit its own handler.
|
import type { SessionUser, UserSelf } from "@goauthentik/api";
|
||||||
type SidebarEntry = [
|
|
||||||
path: string | null,
|
|
||||||
label: string,
|
|
||||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
|
||||||
children?: SidebarEntry[],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
@customElement("ak-admin-sidebar")
|
||||||
* Recursively renders a sidebar entry.
|
export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement)) {
|
||||||
*/
|
@property({ type: Boolean, reflect: true })
|
||||||
export function renderSidebarItem([
|
open = true;
|
||||||
path,
|
|
||||||
label,
|
|
||||||
attributes,
|
|
||||||
children,
|
|
||||||
]: SidebarEntry): TemplateResult {
|
|
||||||
const properties = Array.isArray(attributes)
|
|
||||||
? { ".activeWhen": attributes }
|
|
||||||
: (attributes ?? {});
|
|
||||||
|
|
||||||
if (path) {
|
@state()
|
||||||
properties.path = path;
|
impersonation: UserSelf["username"] | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
me().then((user: SessionUser) => {
|
||||||
|
this.impersonation = user.original ? user.user.username : null;
|
||||||
|
});
|
||||||
|
this.toggleOpen = this.toggleOpen.bind(this);
|
||||||
|
this.checkWidth = this.checkWidth.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<ak-sidebar-item ${spread(properties)}>
|
// This has to be a bound method so the event listener can be removed on disconnection as
|
||||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
// needed.
|
||||||
${children ? renderSidebarItems(children) : nothing}
|
toggleOpen() {
|
||||||
</ak-sidebar-item>`;
|
this.open = !this.open;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"));
|
||||||
|
this.open = window.innerWidth >= minWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
|
||||||
|
window.addEventListener("resize", this.checkWidth);
|
||||||
|
// After connecting to the DOM, we can now perform this check to see if the sidebar should
|
||||||
|
// be open by default.
|
||||||
|
this.checkWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The symmetry (☟, ☝) here is critical in that you want to start adding these handlers after
|
||||||
|
// connection, and removing them before disconnection.
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
window.removeEventListener(EVENT_SIDEBAR_TOGGLE, this.toggleOpen);
|
||||||
|
window.removeEventListener("resize", this.checkWidth);
|
||||||
|
super.disconnectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<ak-sidebar
|
||||||
|
class="pf-c-page__sidebar ${this.open ? "pf-m-expanded" : "pf-m-collapsed"} ${this
|
||||||
|
.activeTheme === UiThemeEnum.Light
|
||||||
|
? "pf-m-light"
|
||||||
|
: ""}"
|
||||||
|
>
|
||||||
|
${this.renderSidebarItems()}
|
||||||
|
</ak-sidebar>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// a browser reflow, which may trigger some other styling the application is monitoring,
|
||||||
|
// triggering a re-render which triggers a browser reflow, ad infinitum. But we've been
|
||||||
|
// living with that since jQuery, and it's both well-known and fortunately rare.
|
||||||
|
|
||||||
|
// eslint-disable-next-line wc/no-self-class
|
||||||
|
this.classList.remove("pf-m-expanded", "pf-m-collapsed");
|
||||||
|
// eslint-disable-next-line wc/no-self-class
|
||||||
|
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, any> | string[] | null, // eslint-disable-line
|
||||||
|
children?: SidebarEntry[],
|
||||||
|
];
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
const sidebarContent: SidebarEntry[] = [
|
||||||
|
[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>${SLUG_REGEX})$`]],
|
||||||
|
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
|
||||||
|
["/outpost/outposts", msg("Outposts")]]],
|
||||||
|
[null, msg("Events"), null, [
|
||||||
|
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
|
||||||
|
["/events/rules", msg("Notification Rules")],
|
||||||
|
["/events/transports", msg("Notification Transports")]]],
|
||||||
|
[null, msg("Customization"), null, [
|
||||||
|
["/policy/policies", msg("Policies")],
|
||||||
|
["/core/property-mappings", msg("Property Mappings")],
|
||||||
|
["/blueprints/instances", msg("Blueprints")],
|
||||||
|
["/policy/reputation", msg("Reputation scores")]]],
|
||||||
|
[null, msg("Flows and Stages"), null, [
|
||||||
|
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
|
||||||
|
["/flow/stages", msg("Stages")],
|
||||||
|
["/flow/stages/prompts", msg("Prompts")]]],
|
||||||
|
[null, msg("Directory"), null, [
|
||||||
|
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
||||||
|
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
||||||
|
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
||||||
|
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
|
||||||
|
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
||||||
|
["/core/tokens", msg("Tokens and App passwords")],
|
||||||
|
["/flow/stages/invitations", msg("Invitations")]]],
|
||||||
|
[null, msg("System"), null, [
|
||||||
|
["/core/brands", msg("Brands")],
|
||||||
|
["/crypto/certificates", msg("Certificates")],
|
||||||
|
["/outpost/integrations", msg("Outpost Integrations")],
|
||||||
|
["/admin/settings", msg("Settings")]]],
|
||||||
|
];
|
||||||
|
|
||||||
|
// 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`<ak-sidebar-item ${spread(properties)}>
|
||||||
|
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||||
|
${map(children, renderOneSidebarItem)}
|
||||||
|
</ak-sidebar-item>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
return html`
|
||||||
|
${map(sidebarContent, renderOneSidebarItem)}
|
||||||
|
${this.renderEnterpriseMenu()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEnterpriseMenu() {
|
||||||
|
return this.can(CapabilitiesEnum.IsEnterprise)
|
||||||
|
? html`
|
||||||
|
<ak-sidebar-item>
|
||||||
|
<span slot="label">${msg("Enterprise")}</span>
|
||||||
|
<ak-sidebar-item path="/enterprise/licenses">
|
||||||
|
<span slot="label">${msg("Licenses")}</span>
|
||||||
|
</ak-sidebar-item>
|
||||||
|
</ak-sidebar-item>
|
||||||
|
`
|
||||||
|
: nothing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
declare global {
|
||||||
* Recursively renders a collection of sidebar entries.
|
interface HTMLElementTagNameMap {
|
||||||
*/
|
"ak-admin-sidebar": AkAdminSidebar;
|
||||||
export function renderSidebarItems(entries: readonly SidebarEntry[]) {
|
}
|
||||||
console.debug("authentik/sidebar: Rendering sidebar items", entries);
|
|
||||||
return repeat(entries, ([path, label]) => path || label, renderSidebarItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
export const AdminSidebarEntries: readonly SidebarEntry[] = [
|
|
||||||
[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>${SLUG_REGEX})$`]],
|
|
||||||
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
|
|
||||||
["/outpost/outposts", msg("Outposts")]]
|
|
||||||
],
|
|
||||||
[null, msg("Events"), null, [
|
|
||||||
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
|
|
||||||
["/events/rules", msg("Notification Rules")],
|
|
||||||
["/events/transports", msg("Notification Transports")]]
|
|
||||||
],
|
|
||||||
[null, msg("Customization"), null, [
|
|
||||||
["/policy/policies", msg("Policies")],
|
|
||||||
["/core/property-mappings", msg("Property Mappings")],
|
|
||||||
["/blueprints/instances", msg("Blueprints")],
|
|
||||||
["/policy/reputation", msg("Reputation scores")]]
|
|
||||||
],
|
|
||||||
[null, msg("Flows and Stages"), null, [
|
|
||||||
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
|
|
||||||
["/flow/stages", msg("Stages")],
|
|
||||||
["/flow/stages/prompts", msg("Prompts")]]
|
|
||||||
],
|
|
||||||
[null, msg("Directory"), null, [
|
|
||||||
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
|
||||||
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
|
||||||
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
|
||||||
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
|
|
||||||
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
|
||||||
["/core/tokens", msg("Tokens and App passwords")],
|
|
||||||
["/flow/stages/invitations", msg("Invitations")]]
|
|
||||||
],
|
|
||||||
[null, msg("System"), null, [
|
|
||||||
["/core/brands", msg("Brands")],
|
|
||||||
["/crypto/certificates", msg("Certificates")],
|
|
||||||
["/outpost/integrations", msg("Outpost Integrations")],
|
|
||||||
["/admin/settings", msg("Settings")]]
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
export const AdminSidebarEnterpriseEntries: readonly SidebarEntry[] = [
|
|
||||||
[null, msg("Enterprise"), null, [
|
|
||||||
["/enterprise/licenses", msg("Licenses"), null]
|
|
||||||
],
|
|
||||||
]]
|
|
||||||
|
|||||||
@ -58,6 +58,9 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
|||||||
PFContent,
|
PFContent,
|
||||||
PFDivider,
|
PFDivider,
|
||||||
css`
|
css`
|
||||||
|
.pf-l-grid__item {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
.pf-l-grid__item.big-graph-container {
|
.pf-l-grid__item.big-graph-container {
|
||||||
height: 35em;
|
height: 35em;
|
||||||
}
|
}
|
||||||
@ -71,10 +74,6 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
|||||||
line-height: normal;
|
line-height: normal;
|
||||||
font-size: var(--pf-global--icon--FontSize--sm);
|
font-size: var(--pf-global--icon--FontSize--sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-item {
|
|
||||||
aspect-ratio: 2 / 1;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -95,31 +94,22 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
const username = this.user?.user.name || this.user?.user.username;
|
const name = this.user?.user.name ?? this.user?.user.username;
|
||||||
|
|
||||||
return html` <ak-page-header
|
return html`<ak-page-header description=${msg("General system status")} ?hasIcon=${false}>
|
||||||
header=${msg(str`Welcome, ${username || ""}.`)}
|
<span slot="header"> ${msg(str`Welcome, ${name || ""}.`)} </span>
|
||||||
description=${msg("General system status")}
|
|
||||||
?hasIcon=${false}
|
|
||||||
>
|
|
||||||
</ak-page-header>
|
</ak-page-header>
|
||||||
<section class="pf-c-page__main-section">
|
<section class="pf-c-page__main-section">
|
||||||
<div class="pf-l-grid pf-m-gutter">
|
<div class="pf-l-grid pf-m-gutter">
|
||||||
<div class="pf-l-grid__item pf-m-12-col pf-m-2-row pf-m-9-col-on-xl">
|
<!-- row 1 -->
|
||||||
<ak-recent-events pageSize="6"></ak-recent-events>
|
<div
|
||||||
</div>
|
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-6-col-on-2xl pf-l-grid pf-m-gutter"
|
||||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-sm pf-m-3-col-on-xl">
|
>
|
||||||
<ak-quick-actions-card .actions=${this.quickActions}>
|
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-4-col-on-2xl">
|
||||||
</ak-quick-actions-card>
|
<ak-quick-actions-card .actions=${this.quickActions}>
|
||||||
</div>
|
</ak-quick-actions-card>
|
||||||
|
</div>
|
||||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-sm pf-m-3-col-on-xl">
|
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-4-col-on-2xl">
|
||||||
<ak-admin-status-version> </ak-admin-status-version>
|
|
||||||
</div>
|
|
||||||
<div class="pf-l-grid pf-l-grid__item pf-m-12-col pf-m-gutter">
|
|
||||||
${this.renderSecondaryRow()}
|
|
||||||
|
|
||||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-md chart-item">
|
|
||||||
<ak-aggregate-card
|
<ak-aggregate-card
|
||||||
icon="pf-icon pf-icon-zone"
|
icon="pf-icon pf-icon-zone"
|
||||||
header=${msg("Outpost status")}
|
header=${msg("Outpost status")}
|
||||||
@ -128,13 +118,24 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
|||||||
<ak-admin-status-chart-outpost></ak-admin-status-chart-outpost>
|
<ak-admin-status-chart-outpost></ak-admin-status-chart-outpost>
|
||||||
</ak-aggregate-card>
|
</ak-aggregate-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-md chart-item">
|
<div
|
||||||
|
class="pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-4-col-on-2xl"
|
||||||
|
>
|
||||||
<ak-aggregate-card icon="fa fa-sync-alt" header=${msg("Sync status")}>
|
<ak-aggregate-card icon="fa fa-sync-alt" header=${msg("Sync status")}>
|
||||||
<ak-admin-status-chart-sync></ak-admin-status-chart-sync>
|
<ak-admin-status-chart-sync></ak-admin-status-chart-sync>
|
||||||
</ak-aggregate-card>
|
</ak-aggregate-card>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pf-l-grid__item pf-m-12-col">
|
||||||
|
<hr class="pf-c-divider" />
|
||||||
|
</div>
|
||||||
|
${this.renderCards()}
|
||||||
|
</div>
|
||||||
|
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl">
|
||||||
|
<ak-recent-events pageSize="6"></ak-recent-events>
|
||||||
|
</div>
|
||||||
|
<div class="pf-l-grid__item pf-m-12-col">
|
||||||
|
<hr class="pf-c-divider" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- row 3 -->
|
<!-- row 3 -->
|
||||||
<div
|
<div
|
||||||
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-8-col-on-2xl big-graph-container"
|
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-8-col-on-2xl big-graph-container"
|
||||||
@ -162,34 +163,32 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
|||||||
</section>`;
|
</section>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSecondaryRow() {
|
renderCards() {
|
||||||
const isEnterprise = this.hasEnterpriseLicense;
|
const isEnterprise = this.hasEnterpriseLicense;
|
||||||
const colSpan = isEnterprise ? 4 : 6;
|
|
||||||
|
|
||||||
const classes = {
|
const classes = {
|
||||||
"card-container": true,
|
"card-container": true,
|
||||||
"pf-l-grid__item": true,
|
"pf-l-grid__item": true,
|
||||||
[`pf-m-12-col`]: true,
|
"pf-m-6-col": true,
|
||||||
[`pf-m-${colSpan}-col-on-md`]: true,
|
"pf-m-4-col-on-md": !isEnterprise,
|
||||||
|
"pf-m-4-col-on-xl": !isEnterprise,
|
||||||
|
"pf-m-3-col-on-md": isEnterprise,
|
||||||
|
"pf-m-3-col-on-xl": isEnterprise,
|
||||||
};
|
};
|
||||||
|
|
||||||
return html`
|
return html`<div class=${classMap(classes)}>
|
||||||
<div class=${classMap(classes)}>
|
|
||||||
<ak-admin-status-system> </ak-admin-status-system>
|
<ak-admin-status-system> </ak-admin-status-system>
|
||||||
</div>
|
</div>
|
||||||
|
<div class=${classMap(classes)}>
|
||||||
|
<ak-admin-status-version> </ak-admin-status-version>
|
||||||
|
</div>
|
||||||
<div class=${classMap(classes)}>
|
<div class=${classMap(classes)}>
|
||||||
<ak-admin-status-card-workers> </ak-admin-status-card-workers>
|
<ak-admin-status-card-workers> </ak-admin-status-card-workers>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${isEnterprise
|
${isEnterprise
|
||||||
? html`
|
? html` <div class=${classMap(classes)}>
|
||||||
<div class=${classMap(classes)}>
|
<ak-admin-fips-status-system> </ak-admin-fips-status-system>
|
||||||
<ak-admin-fips-status-system> </ak-admin-fips-status-system>
|
</div>`
|
||||||
</div>
|
: nothing} `;
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderActions() {
|
renderActions() {
|
||||||
|
|||||||
@ -83,10 +83,13 @@ export class AdminSettingsPage extends AKElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.settings) return nothing;
|
if (!this.settings) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
return html`
|
return html`
|
||||||
<ak-page-header icon="fa fa-cog" header="${msg("System settings")}"> </ak-page-header>
|
<ak-page-header icon="fa fa-cog" header="" description="">
|
||||||
|
<span slot="header"> ${msg("System settings")} </span>
|
||||||
|
</ak-page-header>
|
||||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||||
<div class="pf-c-card">
|
<div class="pf-c-card">
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
|
|||||||
@ -17,13 +17,6 @@
|
|||||||
|
|
||||||
/* Minimum width after which the sidebar becomes automatic */
|
/* Minimum width after which the sidebar becomes automatic */
|
||||||
--ak-sidebar--minimum-auto-width: 80rem;
|
--ak-sidebar--minimum-auto-width: 80rem;
|
||||||
|
|
||||||
/**
|
|
||||||
* The height of the navbar and branded sidebar.
|
|
||||||
* @todo This shouldn't be necessary. The sidebar can instead use a grid layout
|
|
||||||
* ensuring they share the same height.
|
|
||||||
*/
|
|
||||||
--ak-navbar--height: 7rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports selector(::-webkit-scrollbar) {
|
@supports selector(::-webkit-scrollbar) {
|
||||||
|
|||||||
@ -67,12 +67,6 @@ export class NavigationButtons extends AKElement {
|
|||||||
:host([theme="light"]) .pf-c-page__header-tools-group .pf-c-button {
|
:host([theme="light"]) .pf-c-page__header-tools-group .pf-c-button {
|
||||||
color: var(--ak-global--Color--100) !important;
|
color: var(--ak-global--Color--100) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.pf-c-avatar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -162,7 +156,9 @@ export class NavigationButtons extends AKElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderImpersonation() {
|
renderImpersonation() {
|
||||||
if (!this.me?.original) return nothing;
|
if (!this.me?.original) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
const onClick = async () => {
|
const onClick = async () => {
|
||||||
await new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve();
|
await new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve();
|
||||||
@ -179,14 +175,6 @@ export class NavigationButtons extends AKElement {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAvatar() {
|
|
||||||
return html`<img
|
|
||||||
class="pf-c-avatar"
|
|
||||||
src=${ifDefined(this.me?.user.avatar)}
|
|
||||||
alt="${msg("Avatar image")}"
|
|
||||||
/>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get userDisplayName() {
|
get userDisplayName() {
|
||||||
return match<UserDisplay | undefined, string | undefined>(this.uiConfig?.navbar.userDisplay)
|
return match<UserDisplay | undefined, string | undefined>(this.uiConfig?.navbar.userDisplay)
|
||||||
.with(UserDisplay.username, () => this.me?.user.username)
|
.with(UserDisplay.username, () => this.me?.user.username)
|
||||||
@ -224,7 +212,11 @@ export class NavigationButtons extends AKElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
: nothing}
|
: nothing}
|
||||||
${this.renderAvatar()}
|
<img
|
||||||
|
class="pf-c-avatar"
|
||||||
|
src=${ifDefined(this.me?.user.avatar)}
|
||||||
|
alt="${msg("Avatar image")}"
|
||||||
|
/>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,23 +5,20 @@ import {
|
|||||||
} from "@goauthentik/common/constants";
|
} from "@goauthentik/common/constants";
|
||||||
import { globalAK } from "@goauthentik/common/global";
|
import { globalAK } from "@goauthentik/common/global";
|
||||||
import { currentInterface } from "@goauthentik/common/sentry";
|
import { currentInterface } from "@goauthentik/common/sentry";
|
||||||
import { UIConfig, UserDisplay, getConfigForUser } from "@goauthentik/common/ui/config";
|
import { UIConfig, UserDisplay, uiConfig } from "@goauthentik/common/ui/config";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import "@goauthentik/components/ak-nav-buttons";
|
import "@goauthentik/components/ak-nav-buttons";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||||
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
|
|
||||||
import { themeImage } from "@goauthentik/elements/utils/images";
|
|
||||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, LitElement, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
|
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||||
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
|
||||||
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
|
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
|
||||||
import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";
|
import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";
|
||||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||||
@ -29,52 +26,34 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|||||||
|
|
||||||
import { SessionUser } from "@goauthentik/api";
|
import { SessionUser } from "@goauthentik/api";
|
||||||
|
|
||||||
//#region Page Navbar
|
@customElement("ak-page-header")
|
||||||
|
export class PageHeader extends WithBrandConfig(AKElement) {
|
||||||
export interface PageNavbarDetails {
|
@property()
|
||||||
header?: string;
|
|
||||||
description?: string;
|
|
||||||
icon?: string;
|
icon?: string;
|
||||||
iconImage?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@property({ type: Boolean })
|
||||||
* A global navbar component at the top of the page.
|
iconImage = false;
|
||||||
*
|
|
||||||
* Internally, this component listens for the `ak-page-header` event, which is
|
|
||||||
* dispatched by the `ak-page-header` component.
|
|
||||||
*/
|
|
||||||
@customElement("ak-page-navbar")
|
|
||||||
export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavbarDetails {
|
|
||||||
//#region Static Properties
|
|
||||||
|
|
||||||
private static elementRef: AKPageNavbar | null = null;
|
@property()
|
||||||
|
header = "";
|
||||||
|
|
||||||
static readonly setNavbarDetails = (detail: Partial<PageNavbarDetails>): void => {
|
@property()
|
||||||
const { elementRef } = AKPageNavbar;
|
description?: string;
|
||||||
if (!elementRef) {
|
|
||||||
console.debug(
|
|
||||||
`ak-page-header: Could not find ak-page-navbar, skipping event dispatch.`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { header, description, icon, iconImage } = detail;
|
@property({ type: Boolean })
|
||||||
|
hasIcon = true;
|
||||||
|
|
||||||
elementRef.header = header;
|
@state()
|
||||||
elementRef.description = description;
|
me?: SessionUser;
|
||||||
elementRef.icon = icon;
|
|
||||||
elementRef.iconImage = iconImage || false;
|
@state()
|
||||||
elementRef.hasIcon = !!icon;
|
uiConfig!: UIConfig;
|
||||||
};
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
PFBase,
|
PFBase,
|
||||||
PFButton,
|
PFButton,
|
||||||
PFPage,
|
PFPage,
|
||||||
PFDrawer,
|
|
||||||
|
|
||||||
PFNotificationBadge,
|
PFNotificationBadge,
|
||||||
PFContent,
|
PFContent,
|
||||||
PFAvatar,
|
PFAvatar,
|
||||||
@ -84,392 +63,143 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageNavb
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: var(--pf-global--ZIndex--lg);
|
z-index: var(--pf-global--ZIndex--lg);
|
||||||
--pf-c-page__header-tools--MarginRight: 0;
|
|
||||||
--ak-brand-logo-height: var(--pf-global--FontSize--4xl, 2.25rem);
|
|
||||||
--ak-brand-background-color: var(
|
|
||||||
--pf-c-page__sidebar--m-light--BackgroundColor
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
.bar {
|
||||||
:host([theme="dark"]) {
|
|
||||||
--ak-brand-background-color: var(--pf-c-page__sidebar--BackgroundColor);
|
|
||||||
--pf-c-page__sidebar--BackgroundColor: var(--ak-dark-background-light);
|
|
||||||
color: var(--ak-dark-foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
navbar {
|
|
||||||
border-bottom: var(--pf-global--BorderWidth--sm);
|
border-bottom: var(--pf-global--BorderWidth--sm);
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-bottom-color: var(--pf-global--BorderColor--100);
|
border-bottom-color: var(--pf-global--BorderColor--100);
|
||||||
background-color: var(--pf-c-page--BackgroundColor);
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
min-height: 6rem;
|
min-height: 114px;
|
||||||
|
max-height: 114px;
|
||||||
display: grid;
|
background-color: var(--pf-c-page--BackgroundColor);
|
||||||
row-gap: var(--pf-global--spacer--sm);
|
|
||||||
column-gap: var(--pf-global--spacer--sm);
|
|
||||||
grid-template-columns: [brand] auto [toggle] auto [primary] 1fr [secondary] auto;
|
|
||||||
grid-template-rows: auto auto;
|
|
||||||
grid-template-areas:
|
|
||||||
"brand toggle primary secondary"
|
|
||||||
"brand toggle description secondary";
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
row-gap: var(--pf-global--spacer--xs);
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
grid-template-areas:
|
|
||||||
"toggle primary secondary"
|
|
||||||
"toggle description description";
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.pf-c-page__main-section.pf-m-light {
|
||||||
.items {
|
background-color: transparent;
|
||||||
display: block;
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
grid-column: primary;
|
|
||||||
grid-row: primary / description;
|
|
||||||
|
|
||||||
align-content: center;
|
|
||||||
padding-block: var(--pf-global--spacer--md);
|
|
||||||
|
|
||||||
@media (min-width: 426px) {
|
|
||||||
&.block-sibling {
|
|
||||||
padding-block-end: 0;
|
|
||||||
grid-row: primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
padding-block: var(--pf-global--spacer--sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.accent-icon {
|
|
||||||
height: 1em;
|
|
||||||
width: 1em;
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.page-description {
|
|
||||||
grid-area: description;
|
|
||||||
padding-block-end: var(--pf-global--spacer--md);
|
|
||||||
|
|
||||||
@media (max-width: 425px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
|
||||||
text-wrap: balance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.secondary {
|
|
||||||
grid-area: secondary;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
justify-self: end;
|
|
||||||
padding-block: var(--pf-global--spacer--sm);
|
|
||||||
padding-inline-end: var(--pf-global--spacer--sm);
|
|
||||||
|
|
||||||
@media (min-width: 769px) {
|
|
||||||
align-content: center;
|
|
||||||
padding-block: var(--pf-global--spacer--md);
|
|
||||||
padding-inline-end: var(--pf-global--spacer--xl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.pf-c-page__main-section {
|
||||||
.brand {
|
flex-grow: 1;
|
||||||
grid-area: brand;
|
flex-shrink: 1;
|
||||||
background-color: var(--ak-brand-background-color);
|
|
||||||
height: 100%;
|
|
||||||
width: var(--pf-c-page__sidebar--Width);
|
|
||||||
align-items: center;
|
|
||||||
padding-inline: var(--pf-global--spacer--sm);
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
&.pf-m-collapsed {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1279px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
img.pf-icon {
|
||||||
.sidebar-trigger {
|
max-height: 24px;
|
||||||
grid-area: toggle;
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
height: var(--ak-brand-logo-height);
|
|
||||||
|
|
||||||
& img {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-trigger,
|
.sidebar-trigger,
|
||||||
.notification-trigger {
|
.notification-trigger {
|
||||||
font-size: 1.5rem;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-trigger.has-notifications {
|
.notification-trigger.has-notifications {
|
||||||
color: var(--pf-global--active-color--100);
|
color: var(--pf-global--active-color--100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--pf-global--spacer--xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
}
|
}
|
||||||
`,
|
.pf-c-page__header-tools {
|
||||||
];
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.pf-c-page__header-tools-group {
|
||||||
//#endregion
|
height: 100%;
|
||||||
|
}
|
||||||
//#region Properties
|
:host([theme="dark"]) .pf-c-page__header-tools {
|
||||||
|
color: var(--ak-dark-foreground) !important;
|
||||||
@property({ type: String })
|
|
||||||
icon?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
iconImage = false;
|
|
||||||
|
|
||||||
@property({ type: String })
|
|
||||||
header?: string;
|
|
||||||
|
|
||||||
@property({ type: String })
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
hasIcon = true;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
open = true;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
session?: SessionUser;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
uiConfig!: UIConfig;
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Private Methods
|
|
||||||
|
|
||||||
#setTitle(header?: string) {
|
|
||||||
const currentIf = currentInterface();
|
|
||||||
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
|
|
||||||
|
|
||||||
if (currentIf === "admin") {
|
|
||||||
title = `${msg("Admin")} - ${title}`;
|
|
||||||
}
|
|
||||||
// Prepend the header to the title
|
|
||||||
if (header) {
|
|
||||||
title = `${header} - ${title}`;
|
|
||||||
}
|
|
||||||
document.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toggleSidebar() {
|
|
||||||
this.open = !this.open;
|
|
||||||
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Lifecycle
|
|
||||||
|
|
||||||
public connectedCallback(): void {
|
|
||||||
super.connectedCallback();
|
|
||||||
AKPageNavbar.elementRef = this;
|
|
||||||
|
|
||||||
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
|
||||||
this.firstUpdated();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
AKPageNavbar.elementRef = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async firstUpdated() {
|
|
||||||
this.session = await me();
|
|
||||||
this.uiConfig = getConfigForUser(this.session.user);
|
|
||||||
this.uiConfig.navbar.userDisplay = UserDisplay.none;
|
|
||||||
}
|
|
||||||
|
|
||||||
willUpdate() {
|
|
||||||
// Always update title, even if there's no header value set,
|
|
||||||
// as in that case we still need to return to the generic title
|
|
||||||
this.#setTitle(this.header);
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Render
|
|
||||||
|
|
||||||
renderIcon() {
|
|
||||||
if (this.icon) {
|
|
||||||
if (this.iconImage && !this.icon.startsWith("fa://")) {
|
|
||||||
return html`<img class="accent-icon pf-icon" src="${this.icon}" alt="page icon" />`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const icon = this.icon.replaceAll("fa://", "fa ");
|
|
||||||
|
|
||||||
return html`<i class="accent-icon ${icon}"></i>`;
|
|
||||||
}
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): TemplateResult {
|
|
||||||
return html`<navbar aria-label="Main" class="navbar">
|
|
||||||
<aside class="brand ${this.open ? "" : "pf-m-collapsed"}">
|
|
||||||
<a href="#/">
|
|
||||||
<div class="logo">
|
|
||||||
<img
|
|
||||||
src=${themeImage(
|
|
||||||
this.brand?.brandingLogo ?? DefaultBrand.brandingLogo,
|
|
||||||
)}
|
|
||||||
alt="${msg("authentik Logo")}"
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</aside>
|
|
||||||
<button
|
|
||||||
class="sidebar-trigger pf-c-button pf-m-plain"
|
|
||||||
@click=${this.#toggleSidebar}
|
|
||||||
aria-label=${msg("Toggle sidebar")}
|
|
||||||
aria-expanded=${this.open ? "true" : "false"}
|
|
||||||
>
|
|
||||||
<i class="fas fa-bars"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<section
|
|
||||||
class="items primary pf-c-content ${this.description ? "block-sibling" : ""}"
|
|
||||||
>
|
|
||||||
<h1 class="page-title">
|
|
||||||
${this.hasIcon
|
|
||||||
? html`<slot name="icon">${this.renderIcon()}</slot>`
|
|
||||||
: nothing}
|
|
||||||
${this.header}
|
|
||||||
</h1>
|
|
||||||
</section>
|
|
||||||
${this.description
|
|
||||||
? html`<section class="items page-description pf-c-content">
|
|
||||||
<p>${this.description}</p>
|
|
||||||
</section>`
|
|
||||||
: nothing}
|
|
||||||
|
|
||||||
<section class="items secondary">
|
|
||||||
<div class="pf-c-page__header-tools-group">
|
|
||||||
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.session}>
|
|
||||||
<a
|
|
||||||
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
|
||||||
href="${globalAK().api.base}if/user/"
|
|
||||||
slot="extra"
|
|
||||||
>
|
|
||||||
${msg("User interface")}
|
|
||||||
</a>
|
|
||||||
</ak-nav-buttons>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</navbar>
|
|
||||||
<slot></slot>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Page Header
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A page header component, used to display the page title and description.
|
|
||||||
*
|
|
||||||
* Internally, this component dispatches the `ak-page-header` event, which is
|
|
||||||
* listened to by the `ak-page-navbar` component.
|
|
||||||
*
|
|
||||||
* @singleton
|
|
||||||
*/
|
|
||||||
@customElement("ak-page-header")
|
|
||||||
export class AKPageHeader extends LitElement implements PageNavbarDetails {
|
|
||||||
@property({ type: String })
|
|
||||||
header?: string;
|
|
||||||
|
|
||||||
@property({ type: String })
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@property({ type: String })
|
|
||||||
icon?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
iconImage = false;
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback(): void {
|
constructor() {
|
||||||
super.connectedCallback();
|
super();
|
||||||
|
window.addEventListener(EVENT_WS_MESSAGE, () => {
|
||||||
AKPageNavbar.setNavbarDetails({
|
this.firstUpdated();
|
||||||
header: this.header,
|
|
||||||
description: this.description,
|
|
||||||
icon: this.icon,
|
|
||||||
iconImage: this.iconImage,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(): void {
|
async firstUpdated() {
|
||||||
AKPageNavbar.setNavbarDetails({
|
this.me = await me();
|
||||||
header: this.header,
|
this.uiConfig = await uiConfig();
|
||||||
description: this.description,
|
this.uiConfig.navbar.userDisplay = UserDisplay.none;
|
||||||
icon: this.icon,
|
}
|
||||||
iconImage: this.iconImage,
|
|
||||||
});
|
setTitle(header?: string) {
|
||||||
|
const currentIf = currentInterface();
|
||||||
|
let title = this.brand?.brandingTitle || TITLE_DEFAULT;
|
||||||
|
if (currentIf === "admin") {
|
||||||
|
title = `${msg("Admin")} - ${title}`;
|
||||||
|
}
|
||||||
|
// Prepend the header to the title
|
||||||
|
if (header !== undefined && header !== "") {
|
||||||
|
title = `${header} - ${title}`;
|
||||||
|
}
|
||||||
|
document.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
willUpdate() {
|
||||||
|
// Always update title, even if there's no header value set,
|
||||||
|
// as in that case we still need to return to the generic title
|
||||||
|
this.setTitle(this.header);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderIcon() {
|
||||||
|
if (this.icon) {
|
||||||
|
if (this.iconImage && !this.icon.startsWith("fa://")) {
|
||||||
|
return html`<img class="pf-icon" src="${this.icon}" alt="page icon" />`;
|
||||||
|
}
|
||||||
|
const icon = this.icon.replaceAll("fa://", "fa ");
|
||||||
|
return html`<i class=${icon}></i>`;
|
||||||
|
}
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
return html`<div class="bar">
|
||||||
|
<button
|
||||||
|
class="sidebar-trigger pf-c-button pf-m-plain"
|
||||||
|
@click=${() => {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<section class="pf-c-page__main-section pf-m-light">
|
||||||
|
<div class="pf-c-content">
|
||||||
|
<h1>
|
||||||
|
${this.hasIcon
|
||||||
|
? html`<slot name="icon">${this.renderIcon()}</slot> `
|
||||||
|
: nothing}
|
||||||
|
<slot name="header">${this.header}</slot>
|
||||||
|
</h1>
|
||||||
|
${this.description ? html`<p>${this.description}</p>` : html``}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="pf-c-page__header-tools">
|
||||||
|
<div class="pf-c-page__header-tools-group">
|
||||||
|
<ak-nav-buttons .uiConfig=${this.uiConfig} .me=${this.me}>
|
||||||
|
<a
|
||||||
|
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
|
||||||
|
href="${globalAK().api.base}if/user/"
|
||||||
|
slot="extra"
|
||||||
|
>
|
||||||
|
${msg("User interface")}
|
||||||
|
</a>
|
||||||
|
</ak-nav-buttons>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ak-page-header": AKPageHeader;
|
"ak-page-header": PageHeader;
|
||||||
"ak-page-navbar": AKPageNavbar;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,7 +80,6 @@ export class AggregateCard extends AKElement implements IAggregateCard {
|
|||||||
.center-value {
|
.center-value {
|
||||||
font-size: var(--pf-global--icon--FontSize--lg);
|
font-size: var(--pf-global--icon--FontSize--lg);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
place-content: center;
|
|
||||||
}
|
}
|
||||||
.subtext {
|
.subtext {
|
||||||
margin-top: var(--pf-global--spacer--sm);
|
margin-top: var(--pf-global--spacer--sm);
|
||||||
|
|||||||
@ -35,7 +35,10 @@ export class Sidebar extends AKElement {
|
|||||||
.pf-c-nav__section + .pf-c-nav__section {
|
.pf-c-nav__section + .pf-c-nav__section {
|
||||||
--pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm);
|
--pf-c-nav__section--section--MarginTop: var(--pf-global--spacer--sm);
|
||||||
}
|
}
|
||||||
|
.pf-c-nav__list .sidebar-brand {
|
||||||
|
max-height: 82px;
|
||||||
|
margin-bottom: -0.5rem;
|
||||||
|
}
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -67,6 +70,7 @@ export class Sidebar extends AKElement {
|
|||||||
class="pf-c-nav ${this.activeTheme === UiThemeEnum.Light ? "pf-m-light" : ""}"
|
class="pf-c-nav ${this.activeTheme === UiThemeEnum.Light ? "pf-m-light" : ""}"
|
||||||
aria-label=${msg("Global")}
|
aria-label=${msg("Global")}
|
||||||
>
|
>
|
||||||
|
<ak-sidebar-brand></ak-sidebar-brand>
|
||||||
<ul class="pf-c-nav__list">
|
<ul class="pf-c-nav__list">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -1,3 +1,17 @@
|
|||||||
|
import { EVENT_SIDEBAR_TOGGLE } from "@goauthentik/common/constants";
|
||||||
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
|
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||||
|
import { themeImage } from "@goauthentik/elements/utils/images";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
|
import { customElement } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
|
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||||
|
import PFGlobal from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
import { CurrentBrand, UiThemeEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
// If the viewport is wider than MIN_WIDTH, the sidebar
|
// If the viewport is wider than MIN_WIDTH, the sidebar
|
||||||
@ -14,3 +28,79 @@ export const DefaultBrand: CurrentBrand = {
|
|||||||
matchedDomain: "",
|
matchedDomain: "",
|
||||||
defaultLocale: "",
|
defaultLocale: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@customElement("ak-sidebar-brand")
|
||||||
|
export class SidebarBrand extends WithBrandConfig(AKElement) {
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
PFBase,
|
||||||
|
PFGlobal,
|
||||||
|
PFPage,
|
||||||
|
PFButton,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
height: 114px;
|
||||||
|
min-height: 114px;
|
||||||
|
border-bottom: var(--pf-global--BorderWidth--sm);
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-color: var(--pf-global--BorderColor--100);
|
||||||
|
}
|
||||||
|
.pf-c-brand img {
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
button.pf-c-button.sidebar-trigger {
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 0px;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--ak-dark-foreground);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
this.requestUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
return html` ${window.innerWidth <= MIN_WIDTH
|
||||||
|
? html`
|
||||||
|
<button
|
||||||
|
class="sidebar-trigger pf-c-button"
|
||||||
|
@click=${() => {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent(EVENT_SIDEBAR_TOGGLE, {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
<a href="#/" class="pf-c-page__header-brand-link">
|
||||||
|
<div class="pf-c-brand ak-brand">
|
||||||
|
<img
|
||||||
|
src=${themeImage(this.brand?.brandingLogo ?? DefaultBrand.brandingLogo)}
|
||||||
|
alt="${msg("authentik Logo")}"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ak-sidebar-brand": SidebarBrand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user