* This commit abstracts access to the object `rootInterface()?.config?` into a single accessor, `authentikConfig`, that can be mixed into any AKElement object that requires access to it. Since access to `rootInterface()?.config?` is _universally_ used for a single (and repetitive) boolean check, a separate accessor has been provided that converts all calls of the form: ``` javascript rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) ``` into: ``` javascript this.can(CapabilitiesEnum.CanImpersonate) ``` It does this via a Mixin, `WithCapabilitiesConfig`, which understands that these calls only make sense in the context of a running, fully configured authentik instance, and that their purpose is to inform authentik components of a user’s capabilities. The latter is why I don’t feel uncomfortable turning a function call into a method; we should make it explicit that this is a relationship between components. The mixin has a single single field, `[WCC.capabilitiesConfig]`, where its association with the upper-level configuration is made. If that syntax looks peculiar to you, good! I’ve used an explict unique symbol as the field name; it is inaccessable an innumerable in the object list. The debugger shows it only as: Symbol(): { cacheTimeout: 300 cacheTimeoutFlows: 300 cacheTimeoutPolicies: 300 cacheTimeoutReputation: 300 capabilities: (5) ['can_save_media', 'can_geo_ip', 'can_impersonate', 'can_debug', 'is_enterprise'] } Since you can’t reference it by identity, you can’t write to it. Until every browser supports actual private fields, this is the best we can do; it does guarantee that field name collisions are impossible, which is a win. The mixin takes a second optional boolean; setting this to true will cause any web component using the mixin to automatically schedule a re-render if the capabilities list changes. The mixin is also generic; despite the "...into a Lit-Context" in the title, the internals of the Mixin can be replaced with anything so long as the signature of `.can()` is preserved. Because this work builds off the work I did to give the Sidebar access to the configuration without ad-hoc retrieval or prop-drilling, it wasn’t necessary to create a new context for it. That will be necessary for the following: TODO: ``` javascript rootInterface()?.uiConfig; rootInterface()?.tenant; me(); ``` * web: Added a README with a description of the applications' "mental model," essentially an architectural description. * web: prettier had opinions about the README * web: Jens requested that subscription be by default, and it's the right call. * This commit abstracts access to the object `rootInterface()?.config?` into a single accessor, `authentikConfig`, that can be mixed into any AKElement object that requires access to it. Since access to `rootInterface()?.config?` is _universally_ used for a single (and repetitive) boolean check, a separate accessor has been provided that converts all calls of the form: ``` javascript rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate) ``` into: ``` javascript this.can(CapabilitiesEnum.CanImpersonate) ``` It does this via a Mixin, `WithCapabilitiesConfig`, which understands that these calls only make sense in the context of a running, fully configured authentik instance, and that their purpose is to inform authentik components of a user’s capabilities. The latter is why I don’t feel uncomfortable turning a function call into a method; we should make it explicit that this is a relationship between components. The mixin has a single single field, `[WCC.capabilitiesConfig]`, where its association with the upper-level configuration is made. If that syntax looks peculiar to you, good! I’ve used an explict unique symbol as the field name; it is inaccessable an innumerable in the object list. The debugger shows it only as: Symbol(): { cacheTimeout: 300 cacheTimeoutFlows: 300 cacheTimeoutPolicies: 300 cacheTimeoutReputation: 300 capabilities: (5) ['can_save_media', 'can_geo_ip', 'can_impersonate', 'can_debug', 'is_enterprise'] } Since you can’t reference it by identity, you can’t write to it. Until every browser supports actual private fields, this is the best we can do; it does guarantee that field name collisions are impossible, which is a win. The mixin takes a second optional boolean; setting this to true will cause any web component using the mixin to automatically schedule a re-render if the capabilities list changes. The mixin is also generic; despite the "...into a Lit-Context" in the title, the internals of the Mixin can be replaced with anything so long as the signature of `.can()` is preserved. Because this work builds off the work I did to give the Sidebar access to the configuration without ad-hoc retrieval or prop-drilling, it wasn’t necessary to create a new context for it. That will be necessary for the following: TODO: ``` javascript rootInterface()?.uiConfig; rootInterface()?.tenant; me(); ``` * web: Added a README with a description of the applications' "mental model," essentially an architectural description. * web: prettier had opinions about the README * web: Jens requested that subscription be by default, and it's the right call. * web: adjust RAC to point to the (now independent) Interface. - Also, removed redundant check.
56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
import { globalAK } from "@goauthentik/common/global";
|
|
import { Interface } from "@goauthentik/elements/Interface";
|
|
|
|
import { msg } from "@lit/localize";
|
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
|
import { customElement } from "lit/decorators.js";
|
|
|
|
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
|
|
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
|
import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
|
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|
|
|
import { UiThemeEnum } from "@goauthentik/api";
|
|
|
|
@customElement("ak-loading")
|
|
export class Loading extends Interface {
|
|
static get styles(): CSSResult[] {
|
|
return [
|
|
PFBase,
|
|
PFPage,
|
|
PFSpinner,
|
|
PFEmptyState,
|
|
css`
|
|
:host([theme="dark"]) h1 {
|
|
color: var(--ak-dark-foreground);
|
|
}
|
|
`,
|
|
];
|
|
}
|
|
|
|
async getTheme(): Promise<UiThemeEnum> {
|
|
return globalAK()?.tenant.uiTheme || UiThemeEnum.Automatic;
|
|
}
|
|
|
|
render(): TemplateResult {
|
|
return html` <section
|
|
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
|
|
>
|
|
<div class="pf-c-empty-state" style="height: 100vh;">
|
|
<div class="pf-c-empty-state__content">
|
|
<span
|
|
class="pf-c-spinner pf-m-xl"
|
|
role="progressbar"
|
|
aria-valuetext="${msg("Loading...")}"
|
|
>
|
|
<span class="pf-c-spinner__clipper"></span>
|
|
<span class="pf-c-spinner__lead-ball"></span>
|
|
<span class="pf-c-spinner__tail-ball"></span>
|
|
</span>
|
|
<h1 class="pf-c-title pf-m-lg">${msg("Loading...")}</h1>
|
|
</div>
|
|
</div>
|
|
</section>`;
|
|
}
|
|
}
|