From 40caedfbd06a9001255af500fefcf58f2892a99e Mon Sep 17 00:00:00 2001 From: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:57:08 +0200 Subject: [PATCH] web: Fix user list default paths. (#23062) --- web/src/admin/users/UserListPage.ts | 61 ++++++-- web/src/common/ui/config.ts | 2 +- web/src/elements/TreeView.ts | 219 +++++++++++++++------------- 3 files changed, 165 insertions(+), 117 deletions(-) diff --git a/web/src/admin/users/UserListPage.ts b/web/src/admin/users/UserListPage.ts index c362b3ff20..b2ce44498a 100644 --- a/web/src/admin/users/UserListPage.ts +++ b/web/src/admin/users/UserListPage.ts @@ -98,9 +98,12 @@ export class UserListPage extends WithLicenseSummary( @property({ type: String }) public order = "-last_login"; - @property({ type: String, useDefault: true }) + @property({ type: String, attribute: "active-path", useDefault: true }) public activePath: string = DefaultUIConfig.defaults.userPath; + @property({ type: String, attribute: "default-active-path", useDefault: true }) + public defaultActivePath: string = DefaultUIConfig.defaults.userPath; + @state() protected hideDeactivated = getURLParam("hideDeactivated", false); @@ -109,18 +112,39 @@ export class UserListPage extends WithLicenseSummary( protected canImpersonate = false; - public override connectedCallback(): void { - super.connectedCallback(); + //#region Lifecycle + /** + * Synchronizes `activePath` and `defaultActivePath` from three sources in priority order: + * + * 1. URL param (explicit navigation) + * 2. Brand default user path (admin-configured override) + * 3. Compiled-in `DefaultUIConfig` default (fallback) + * + * `activePath` is set to `""` (show all users) when neither a URL param nor a + * brand-level override is present, avoiding silent list filtering. + * `defaultActivePath` always resolves to a value via the fallback chain. + */ + protected synchronizeUserPaths(): void { this.canImpersonate = this.can(CapabilitiesEnum.CanImpersonate); const initialDefaultUserPath = DefaultUIConfig.defaults.userPath; const brandDefaultUserPath = this.uiConfig.defaults.userPath; + const defaultUserPath = brandDefaultUserPath || initialDefaultUserPath; + const userPathParam = getURLParam("path", ""); - this.activePath = getURLParam( - "path", - brandDefaultUserPath || initialDefaultUserPath, - ); + const pathPresent = + (userPathParam && userPathParam !== "") || defaultUserPath !== initialDefaultUserPath; + + const resolvedUserPath = pathPresent ? userPathParam || defaultUserPath : ""; + + this.activePath = resolvedUserPath; + this.defaultActivePath = userPathParam || defaultUserPath; + } + + public override connectedCallback(): void { + super.connectedCallback(); + this.synchronizeUserPaths(); } protected override async apiEndpoint(): Promise> { @@ -138,6 +162,17 @@ export class UserListPage extends WithLicenseSummary( return users; } + //#endregion + + //#region Event Listeners + + protected treeViewRefreshListener = (ev: CustomEvent<{ path: string }>) => { + this.activePath = ev.detail.path; + this.defaultActivePath = ev.detail.path; + }; + + //#endregion + protected buildExportParams = async (): Promise => { return { ...(await this.defaultEndpointConfig()), @@ -346,15 +381,15 @@ export class UserListPage extends WithLicenseSummary( } protected renderObjectCreate(): SlottedTemplateResult { - const { activePath } = this; + const { defaultActivePath } = this; - return guard([activePath], () => { + return guard([defaultActivePath], () => { return [ html`` - : nothing} - - - - - + return html`
  • +
    +
    +
    + ${this.openable + ? html` ` + : null} + + + +
    -
      - ${this.item?.childItems.map((item) => { - return html``; - })} -
    -
  • - `; + +
      + ${this.item?.childItems.map((item) => { + return html``; + })} +
    + `; } } +//#endregion + +//#region Tree View + @customElement("ak-treeview") export class TreeView extends AKElement { static styles: CSSResult[] = [PFTreeView]; @@ -171,61 +178,67 @@ export class TreeView extends AKElement { public label: string | null = null; @property({ type: Array }) - items: string[] = []; + public items: string[] = []; - @property() - activePath = ""; + @property({ type: String, attribute: "default-active-path", useDefault: true }) + public defaultActivePath = ""; - @state() - activeNode?: TreeViewNode; + @property({ attribute: false }) + public activeNode: TreeViewNode | null = null; - separator = "/"; + protected separator = "/"; + + public createNode(path: string[], parentItem: TreeViewItem, level: number): TreeViewItem { + const id = path.shift() || null; + const idx = parentItem.childItems.findIndex((item) => item.id === id); - createNode(path: string[], parentItem: TreeViewItem, level: number): TreeViewItem { - const id = path.shift(); - const idx = parentItem.childItems.findIndex((e: TreeViewItem) => { - return e.id === id; - }); if (idx < 0) { const item: TreeViewItem = { - id: id, + id, label: id || "", childItems: [], level: level, parent: parentItem, }; + parentItem.childItems.push(item); - if (path.length !== 0) { + + if (path.length) { const child = this.createNode(path, item, level + 1); child.parent = item; } return item; } + return this.createNode(path, parentItem.childItems[idx], level + 1); } - parse(data: string[]): TreeViewItem { + protected parse(data: string[]): TreeViewItem { const rootItem: TreeViewItem = { - id: undefined, + id: null, label: msg("Root"), childItems: [], level: -1, }; + for (let i = 0; i < data.length; i++) { const path: string = data[i]; const split: string[] = path.split(this.separator); + this.createNode(split, rootItem, 0); } + return rootItem; } - render(): TemplateResult { + protected override render(): SlottedTemplateResult { const rootItem = this.parse(this.items); + return html`