Skip to content

Commit

Permalink
Merge branch 'main' into dev
Browse files Browse the repository at this point in the history
* main:
  web: move context controllers into reactive controller plugins (#8996)
  web: maintenance: split tsconfig into “base” and “build” variants. (#9036)
  web: consistent style declarations internally (#9077)
  • Loading branch information
kensternberg-authentik committed Mar 29, 2024
2 parents fc00bde + 2c64f72 commit 7123b2c
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 177 deletions.
17 changes: 17 additions & 0 deletions web/docs/Changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### 2024-03-26T09:25:06-0700

Split the tsconfig file into a base and build variant.

Lesson: This lesson is stored here and not in a comment in tsconfig.json because
JSON doesn't like comments. Doug Crockford's purity requirement has doomed an
entire generation to keeping its human-facing meta somewhere other than in the
file where it belongs.

Lesson: The `extend` command of tsconfig has an unexpected behavior. It is
neither a merge or a replace, but some mixture of the two. The buildfile's
`compilerOptions` is not a full replacement; instead, each of _its_ top-level
fields is a replacement for what is found in the basefile. So while you don't
need to include _everything_ in a `compilerOptions` field if you want to change
one thing, if you want to modify _one_ path in `compilerOptions.path`, you must
include the entire `compilerOptions.path` collection in your buildfile.
g
11 changes: 1 addition & 10 deletions web/src/common/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
EventMiddleware,
LoggingMiddleware,
} from "@goauthentik/common/api/middleware";
import { EVENT_LOCALE_REQUEST, EVENT_REFRESH, VERSION } from "@goauthentik/common/constants";
import { EVENT_LOCALE_REQUEST, VERSION } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";

import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
Expand Down Expand Up @@ -86,13 +86,4 @@ export function AndNext(url: string): string {
return `?next=${encodeURIComponent(url)}`;
}

window.addEventListener(EVENT_REFRESH, () => {
// Upon global refresh, disregard whatever was pre-hydrated and
// actually load info from API
globalConfigPromise = undefined;
globalBrandPromise = undefined;
config();
brand();
});

console.debug(`authentik(early): version ${VERSION}, apiBase ${DEFAULT_CONFIG.basePath}`);
4 changes: 3 additions & 1 deletion web/src/elements/AuthentikContexts.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { createContext } from "@lit/context";

import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api";
import type { Config, CurrentBrand, LicenseSummary, SessionUser } from "@goauthentik/api";

export const authentikConfigContext = createContext<Config>(Symbol("authentik-config-context"));

export const authentikUserContext = createContext<SessionUser>(Symbol("authentik-user-context"));

export const authentikEnterpriseContext = createContext<LicenseSummary>(
Symbol("authentik-enterprise-context"),
);
Expand Down
52 changes: 52 additions & 0 deletions web/src/elements/Interface/BrandContextController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";

import { ContextProvider } from "@lit/context";
import { ReactiveController, ReactiveControllerHost } from "lit";

import type { CurrentBrand } from "@goauthentik/api";
import { CoreApi } from "@goauthentik/api";

import type { AkInterface } from "./Interface";

type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface;

export class BrandContextController implements ReactiveController {
host!: ReactiveElementHost;

context!: ContextProvider<{ __context__: CurrentBrand | undefined }>;

constructor(host: ReactiveElementHost) {
this.host = host;
this.context = new ContextProvider(this.host, {
context: authentikBrandContext,
initialValue: undefined,
});
this.fetch = this.fetch.bind(this);
this.fetch();
}

fetch() {
new CoreApi(DEFAULT_CONFIG).coreBrandsCurrentRetrieve().then((brand) => {
this.context.setValue(brand);
this.host.brand = brand;
});
}

hostConnected() {
window.addEventListener(EVENT_REFRESH, this.fetch);
}

hostDisconnected() {
window.removeEventListener(EVENT_REFRESH, this.fetch);
}

hostUpdate() {
// If the Interface changes its brand information for some reason,
// we should notify all users of the context of that change. doesn't
if (this.host.brand !== this.context.value) {
this.context.setValue(this.host.brand);
}
}
}
53 changes: 53 additions & 0 deletions web/src/elements/Interface/ConfigContextController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";

import { ContextProvider } from "@lit/context";
import { ReactiveController, ReactiveControllerHost } from "lit";

import type { Config } from "@goauthentik/api";
import { RootApi } from "@goauthentik/api";

import type { AkInterface } from "./Interface";

type ReactiveElementHost = Partial<ReactiveControllerHost> & AkInterface;

export class ConfigContextController implements ReactiveController {
host!: ReactiveElementHost;

context!: ContextProvider<{ __context__: Config | undefined }>;

constructor(host: ReactiveElementHost) {
this.host = host;
this.context = new ContextProvider(this.host, {
context: authentikConfigContext,
initialValue: undefined,
});
this.fetch = this.fetch.bind(this);
this.fetch();
}

fetch() {
new RootApi(DEFAULT_CONFIG).rootConfigRetrieve().then((config) => {
this.context.setValue(config);
this.host.config = config;
});
}

hostConnected() {
window.addEventListener(EVENT_REFRESH, this.fetch);
}

hostDisconnected() {
window.removeEventListener(EVENT_REFRESH, this.fetch);
}

hostUpdate() {
// If the Interface changes its config information, we should notify all
// users of the context of that change, without creating an infinite
// loop of resets.
if (this.host.config !== this.context.value) {
this.context.setValue(this.host.config);
}
}
}
53 changes: 53 additions & 0 deletions web/src/elements/Interface/EnterpriseContextController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/authentik/common/constants";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts";

import { ContextProvider } from "@lit/context";
import { ReactiveController, ReactiveControllerHost } from "lit";

import type { LicenseSummary } from "@goauthentik/api";
import { EnterpriseApi } from "@goauthentik/api";

import type { AkEnterpriseInterface } from "./Interface";

type ReactiveElementHost = Partial<ReactiveControllerHost> & AkEnterpriseInterface;

export class EnterpriseContextController implements ReactiveController {
host!: ReactiveElementHost;

context!: ContextProvider<{ __context__: LicenseSummary | undefined }>;

constructor(host: ReactiveElementHost) {
this.host = host;
this.context = new ContextProvider(this.host, {
context: authentikEnterpriseContext,
initialValue: undefined,
});
this.fetch = this.fetch.bind(this);
this.fetch();
}

fetch() {
new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((enterprise) => {
this.context.setValue(enterprise);
this.host.licenseSummary = enterprise;
});
}

hostConnected() {
window.addEventListener(EVENT_REFRESH_ENTERPRISE, this.fetch);
}

hostDisconnected() {
window.removeEventListener(EVENT_REFRESH_ENTERPRISE, this.fetch);
}

hostUpdate() {
// If the Interface changes its config information, we should notify all
// users of the context of that change, without creating an infinite
// loop of resets.
if (this.host.licenseSummary !== this.context.value) {
this.context.setValue(this.host.licenseSummary);
}
}
}
93 changes: 23 additions & 70 deletions web/src/elements/Interface/Interface.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,47 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { brand, config } from "@goauthentik/common/api/config";
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants";
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
import {
authentikBrandContext,
authentikConfigContext,
authentikEnterpriseContext,
} from "@goauthentik/elements/AuthentikContexts";
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";

import { ContextProvider } from "@lit/context";
import { state } from "lit/decorators.js";

import PFBase from "@patternfly/patternfly/patternfly-base.css";

import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api";
import { EnterpriseApi, UiThemeEnum } from "@goauthentik/api";
import { UiThemeEnum } from "@goauthentik/api";

import { AKElement } from "../Base";
import { BrandContextController } from "./BrandContextController";
import { ConfigContextController } from "./ConfigContextController";
import { EnterpriseContextController } from "./EnterpriseContextController";

type AkInterface = HTMLElement & {
export type AkInterface = HTMLElement & {
getTheme: () => Promise<UiThemeEnum>;
brand?: CurrentBrand;
uiConfig?: UIConfig;
config?: Config;
};

const brandContext = Symbol("brandContext");
const configContext = Symbol("configContext");

export class Interface extends AKElement implements AkInterface {
@state()
uiConfig?: UIConfig;

_configContext = new ContextProvider(this, {
context: authentikConfigContext,
initialValue: undefined,
});
[brandContext]!: BrandContextController;

_config?: Config;
[configContext]!: ConfigContextController;

@state()
set config(c: Config) {
this._config = c;
this._configContext.setValue(c);
this.requestUpdate();
}

get config(): Config | undefined {
return this._config;
}

_brandContext = new ContextProvider(this, {
context: authentikBrandContext,
initialValue: undefined,
});

_brand?: CurrentBrand;
config?: Config;

@state()
set brand(c: CurrentBrand) {
this._brand = c;
this._brandContext.setValue(c);
this.requestUpdate();
}

get brand(): CurrentBrand | undefined {
return this._brand;
}
brand?: CurrentBrand;

constructor() {
super();
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
brand().then((brand) => (this.brand = brand));
config().then((config) => (this.config = config));

this[brandContext] = new BrandContextController(this);
this[configContext] = new ConfigContextController(this);
this.dataset.akInterfaceRoot = "true";
}

Expand All @@ -88,37 +58,20 @@ export class Interface extends AKElement implements AkInterface {
}
}

export class EnterpriseAwareInterface extends Interface {
_licenseSummaryContext = new ContextProvider(this, {
context: authentikEnterpriseContext,
initialValue: undefined,
});
export type AkEnterpriseInterface = AkInterface & {
licenseSummary?: LicenseSummary;
};

_licenseSummary?: LicenseSummary;
const enterpriseContext = Symbol("enterpriseContext");

@state()
set licenseSummary(c: LicenseSummary) {
this._licenseSummary = c;
this._licenseSummaryContext.setValue(c);
this.requestUpdate();
}
export class EnterpriseAwareInterface extends Interface {
[enterpriseContext]!: EnterpriseContextController;

get licenseSummary(): LicenseSummary | undefined {
return this._licenseSummary;
}
@state()
licenseSummary?: LicenseSummary;

constructor() {
super();
const refreshStatus = () => {
new EnterpriseApi(DEFAULT_CONFIG)
.enterpriseLicenseSummaryRetrieve()
.then((enterprise) => {
this.licenseSummary = enterprise;
});
};
refreshStatus();
window.addEventListener(EVENT_REFRESH_ENTERPRISE, () => {
refreshStatus();
});
this[enterpriseContext] = new EnterpriseContextController(this);
}
}
31 changes: 15 additions & 16 deletions web/src/user/LibraryPage/ApplicationEmptyState.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { docLink } from "@goauthentik/common/global";
import { adaptCSS } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";

Expand All @@ -20,23 +19,23 @@ import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
* administrator, provide a link to the "Create a new application" page.
*/

const styles = adaptCSS([
PFBase,
PFEmptyState,
PFButton,
PFContent,
PFSpacing,
css`
.cta {
display: inline-block;
font-weight: bold;
}
`,
]);

@customElement("ak-library-application-empty-list")
export class LibraryPageApplicationEmptyList extends AKElement {
static styles = styles;
static get styles() {
return [
PFBase,
PFEmptyState,
PFButton,
PFContent,
PFSpacing,
css`
.cta {
display: inline-block;
font-weight: bold;
}
`,
];
}

@property({ attribute: "isadmin", type: Boolean })
isAdmin = false;
Expand Down
Loading

0 comments on commit 7123b2c

Please sign in to comment.