diff --git a/packages/prettier-config/lib/constants.js b/packages/prettier-config/lib/constants.js index 1f5f9fec1d..30976e7d26 100644 --- a/packages/prettier-config/lib/constants.js +++ b/packages/prettier-config/lib/constants.js @@ -31,8 +31,33 @@ export const AuthentikPrettierConfig = { trailingComma: "all", useTabs: false, vueIndentScriptAndStyle: false, - plugins: ["prettier-plugin-packagejson", "@trivago/prettier-plugin-sort-imports"], - importOrder: ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"], + plugins: [ + // --- + "prettier-plugin-packagejson", + "@trivago/prettier-plugin-sort-imports", + ], + importOrder: [ + // --- + + "^(@goauthentik/|#)common.+", + "^(@goauthentik/|#)elements.+", + "^(@goauthentik/|#)components.+", + "^(@goauthentik/|#)user.+", + "^(@goauthentik/|#)admin.+", + "^(@goauthentik/|#)flow.+", + "^(@goauthentik/|#)flow.+", + + "^#.+", + "^@goauthentik.+", + + "", + + "^(@?)lit(.*)$", + "\\.css$", + "^@goauthentik/api$", + "^[./]", + ], + importOrderSideEffects: false, importOrderSeparation: true, importOrderSortSpecifiers: true, importOrderParserPlugins: ["typescript", "jsx", "classProperties", "decorators-legacy"], diff --git a/packages/prettier-config/package-lock.json b/packages/prettier-config/package-lock.json index 36228a2647..f908d13b9f 100644 --- a/packages/prettier-config/package-lock.json +++ b/packages/prettier-config/package-lock.json @@ -1,12 +1,12 @@ { "name": "@goauthentik/prettier-config", - "version": "1.0.5", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@goauthentik/prettier-config", - "version": "1.0.5", + "version": "2.0.0", "license": "MIT", "devDependencies": { "@goauthentik/tsconfig": "^1.0.1", diff --git a/packages/prettier-config/package.json b/packages/prettier-config/package.json index 3897f64aca..f9557607d9 100644 --- a/packages/prettier-config/package.json +++ b/packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@goauthentik/prettier-config", - "version": "1.0.5", + "version": "2.0.0", "description": "authentik's Prettier config", "license": "MIT", "scripts": { diff --git a/web/bundler/mdx-plugin/node.js b/web/bundler/mdx-plugin/node.js index 1e899298f8..d5ef33bec4 100644 --- a/web/bundler/mdx-plugin/node.js +++ b/web/bundler/mdx-plugin/node.js @@ -4,30 +4,27 @@ * @import { * OnLoadArgs, * OnLoadResult, + * OnResolveArgs, + * OnResolveResult, * Plugin, * PluginBuild * } from "esbuild" */ +import { MonoRepoRoot } from "@goauthentik/core/paths/node"; import * as fs from "node:fs/promises"; import * as path from "node:path"; /** - * @typedef {Omit & LoadDataFields} LoadData - * Data passed to `onload`. + * @typedef {Omit & LoadDataFields} LoadData Data passed to `onload`. * - * @typedef LoadDataFields - * Extra fields given in `data` to `onload`. - * @property {PluginData | null | undefined} [pluginData] - * Plugin data. + * @typedef LoadDataFields Extra fields given in `data` to `onload`. + * @property {PluginData | null | undefined} [pluginData] Plugin data. * - * - * @typedef PluginData - * Extra data passed. - * @property {Buffer | string | null | undefined} [contents] - * File contents. + * @typedef PluginData Extra data passed. + * @property {Buffer | string | null | undefined} [contents] File contents. */ -const name = "mdx-plugin"; +const pluginName = "mdx-plugin"; /** * @typedef MDXPluginOptions @@ -38,28 +35,35 @@ const name = "mdx-plugin"; /** * Bundle MDX into JSON modules. * - * @param {MDXPluginOptions} options Options. - * @returns {Plugin} Plugin. + * @param {MDXPluginOptions} options + * @returns {Plugin} */ export function mdxPlugin({ root }) { - return { name, setup }; + // TODO: Replace with `resolvePackage` after NPM Workspaces support is added. + const docsPackageRoot = path.resolve(MonoRepoRoot, "website"); /** * @param {PluginBuild} build - * Build. - * @returns {undefined} - * Nothing. */ function setup(build) { - build.onLoad({ filter: /\.mdx?$/ }, onload); + /** + * @param {OnResolveArgs} args + * @returns {Promise} + */ + async function resolveListener(args) { + if (!args.path.startsWith("~")) return args; + + return { + path: path.resolve(docsPackageRoot, args.path.slice(1)), + pluginName, + }; + } /** * @param {LoadData} data - * Data. * @returns {Promise} - * Result. */ - async function onload(data) { + async function loadListener(data) { const content = String( data.pluginData && data.pluginData.contents !== null && @@ -77,7 +81,16 @@ export function mdxPlugin({ root }) { return { contents: JSON.stringify({ content, publicPath, publicDirectory }), loader: "file", + pluginName, }; } + + build.onResolve({ filter: /\.mdx?$/ }, resolveListener); + build.onLoad({ filter: /\.mdx?$/ }, loadListener); } + + return { + name: pluginName, + setup, + }; } diff --git a/web/package.json b/web/package.json index 44ed2ffb86..936ecb596e 100644 --- a/web/package.json +++ b/web/package.json @@ -67,6 +67,7 @@ "#admin/*": "./src/admin/*.js", "#flow/*.css": "./src/flow/*.css", "#flow/*": "./src/flow/*.js", + "#locales/*": "./src/locales/*.js", "#stories/*": "./src/stories/*.js", "#*/browser": { "types": "./out/*/browser.d.ts", diff --git a/web/paths/index.js b/web/paths/index.js new file mode 100644 index 0000000000..eb795a1d2f --- /dev/null +++ b/web/paths/index.js @@ -0,0 +1,19 @@ +/** + * @file Paths used by the web package. + * + * @runtime common + */ + +/** + * The name of the distribution directory. + * + * @runtime common + */ +export const DistDirectoryName = "dist"; + +/** + * The name of the static file directory. + * + * @runtime common + */ +export const StaticDirectoryName = "static"; diff --git a/web/paths/node.js b/web/paths/node.js index 7570231833..14eb449b98 100644 --- a/web/paths/node.js +++ b/web/paths/node.js @@ -1,3 +1,9 @@ +/** + * @file Paths used by the web package. + * + * @runtime node + */ +import { DistDirectoryName } from "#paths"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -11,18 +17,17 @@ const relativeDirname = dirname(fileURLToPath(import.meta.url)); /** * The root of the web package. + * + * @runtime node */ export const PackageRoot = /** @type {WebPackageIdentifier} */ (resolve(relativeDirname, "..")); -/** - * The name of the distribution directory. - */ -export const DistDirectoryName = "dist"; - /** * Path to the web package's distribution directory. * * This is where the built files are located after running the build process. + * + * @runtime node */ export const DistDirectory = /** @type {`${WebPackageIdentifier}/${DistDirectoryName}`} */ ( resolve(PackageRoot, DistDirectoryName) @@ -43,6 +48,8 @@ export const DistDirectory = /** @type {`${WebPackageIdentifier}/${DistDirectory * Entry points available for building. * * @satisfies {Record} + * + * @runtime node */ export const EntryPoint = /** @type {const} */ ({ Admin: { diff --git a/web/scripts/build-web.mjs b/web/scripts/build-web.mjs index 8ee5bff3d1..2a768709f0 100644 --- a/web/scripts/build-web.mjs +++ b/web/scripts/build-web.mjs @@ -6,7 +6,8 @@ */ import { mdxPlugin } from "#bundler/mdx-plugin/node"; import { createBundleDefinitions } from "#bundler/utils/node"; -import { DistDirectory, DistDirectoryName, EntryPoint, PackageRoot } from "#paths/node"; +import { DistDirectoryName } from "#paths"; +import { DistDirectory, EntryPoint, PackageRoot } from "#paths/node"; import { NodeEnvironment } from "@goauthentik/core/environment/node"; import { MonoRepoRoot, resolvePackage } from "@goauthentik/core/paths/node"; import { readBuildIdentifier } from "@goauthentik/core/version/node"; @@ -26,7 +27,7 @@ const patternflyPath = resolvePackage("@patternfly/patternfly", import.meta); */ const BASE_ESBUILD_OPTIONS = { entryNames: `[dir]/[name]-${readBuildIdentifier()}`, - chunkNames: "[dir]/chunks/[name]-[hash]", + chunkNames: "[dir]/chunks/[hash]", assetNames: "assets/[dir]/[name]-[hash]", publicPath: path.join("/static", DistDirectoryName), outdir: DistDirectory, diff --git a/web/src/admin/AdminInterface/AboutModal.ts b/web/src/admin/AdminInterface/AboutModal.ts index d5b7e7e71a..d9b783cf5e 100644 --- a/web/src/admin/AdminInterface/AboutModal.ts +++ b/web/src/admin/AdminInterface/AboutModal.ts @@ -1,9 +1,8 @@ +import { WithBrandConfig } from "#elements/mixins/branding"; +import { WithLicenseSummary } from "#elements/mixins/license"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { globalAK } from "@goauthentik/common/global"; -import { DefaultBrand } from "@goauthentik/common/ui/config"; import "@goauthentik/elements/EmptyState"; -import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; -import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; import { ModalButton } from "@goauthentik/elements/buttons/ModalButton"; import { msg } from "@lit/localize"; @@ -57,7 +56,8 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton)) } renderModal() { - let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle; + let product = this.brandingTitle; + if (this.licenseSummary.status !== LicenseSummaryStatusEnum.Unlicensed) { product += ` ${msg("Enterprise")}`; } @@ -73,7 +73,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
${msg(
diff --git a/web/src/admin/AdminInterface/index.entrypoint.ts b/web/src/admin/AdminInterface/index.entrypoint.ts index a5196bb896..f461a05230 100644 --- a/web/src/admin/AdminInterface/index.entrypoint.ts +++ b/web/src/admin/AdminInterface/index.entrypoint.ts @@ -1,9 +1,12 @@ +import "#admin/AdminInterface/AboutModal"; +import type { AboutModal } from "#admin/AdminInterface/AboutModal"; +import { ROUTES } from "#admin/Routes"; import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE } from "#common/constants"; import { configureSentry } from "#common/sentry/index"; import { me } from "#common/users"; import { WebsocketClient } from "#common/ws"; -import { AuthenticatedInterface } from "#elements/Interface/Interface"; -import { WithLicenseSummary } from "#elements/Interface/licenseSummaryProvider"; +import { SidebarToggleEventDetail } from "#components/ak-page-header"; +import { AuthenticatedInterface } from "#elements/AuthenticatedInterface"; import "#elements/ak-locale-context/ak-locale-context"; import "#elements/banner/EnterpriseStatusBanner"; import "#elements/banner/EnterpriseStatusBanner"; @@ -11,16 +14,13 @@ import "#elements/banner/VersionBanner"; import "#elements/banner/VersionBanner"; import "#elements/messages/MessageContainer"; import "#elements/messages/MessageContainer"; +import { WithCapabilitiesConfig } from "#elements/mixins/capabilities"; import "#elements/notifications/APIDrawer"; import "#elements/notifications/NotificationDrawer"; import { getURLParam, updateURLParams } from "#elements/router/RouteMatch"; import "#elements/router/RouterOutlet"; import "#elements/sidebar/Sidebar"; import "#elements/sidebar/SidebarItem"; -import "@goauthentik/admin/AdminInterface/AboutModal"; -import type { AboutModal } from "@goauthentik/admin/AdminInterface/AboutModal"; -import { ROUTES } from "@goauthentik/admin/Routes"; -import { SidebarToggleEventDetail } from "@goauthentik/components/ak-page-header.js"; import { CSSResult, TemplateResult, css, html, nothing } from "lit"; import { customElement, eventOptions, property, query } from "lit/decorators.js"; @@ -45,7 +45,7 @@ if (process.env.NODE_ENV === "development") { } @customElement("ak-interface-admin") -export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) { +export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterface) { //#region Properties @property({ type: Boolean }) @@ -202,7 +202,7 @@ export class AdminInterface extends WithLicenseSummary(AuthenticatedInterface) { ${renderSidebarItems(AdminSidebarEntries)} - ${this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise) + ${this.can(CapabilitiesEnum.IsEnterprise) ? renderSidebarItems(AdminSidebarEnterpriseEntries) : nothing} diff --git a/web/src/admin/admin-overview/AdminOverviewPage.ts b/web/src/admin/admin-overview/AdminOverviewPage.ts index 5ba4ce6b9b..bb114d1aef 100644 --- a/web/src/admin/admin-overview/AdminOverviewPage.ts +++ b/web/src/admin/admin-overview/AdminOverviewPage.ts @@ -11,10 +11,10 @@ import "#admin/admin-overview/charts/SyncStatusChart"; import { me } from "#common/users"; import "#components/ak-page-header"; import { AKElement } from "#elements/Base"; -import { WithLicenseSummary } from "#elements/Interface/licenseSummaryProvider"; import "#elements/cards/AggregatePromiseCard"; import type { QuickAction } from "#elements/cards/QuickActionsCard"; import "#elements/cards/QuickActionsCard"; +import { WithLicenseSummary } from "#elements/mixins/license"; import { paramURL } from "#elements/router/RouterOutlet"; import { createReleaseNotesURL } from "@goauthentik/core/version"; diff --git a/web/src/admin/applications/ApplicationForm.ts b/web/src/admin/applications/ApplicationForm.ts index 957fb4053e..00d77fcf58 100644 --- a/web/src/admin/applications/ApplicationForm.ts +++ b/web/src/admin/applications/ApplicationForm.ts @@ -1,3 +1,4 @@ +import { CapabilitiesEnum, WithCapabilitiesConfig } from "#elements/mixins/capabilities"; import "@goauthentik/admin/applications/ProviderSelectModal"; import { iconHelperText } from "@goauthentik/admin/helperText"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; @@ -6,18 +7,14 @@ import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-textarea-input"; -import "@goauthentik/elements/Alert.js"; -import { - CapabilitiesEnum, - WithCapabilitiesConfig, -} from "@goauthentik/elements/Interface/capabilitiesProvider"; +import "@goauthentik/elements/Alert"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/ModalForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import "@goauthentik/elements/forms/ProxyForm"; import "@goauthentik/elements/forms/Radio"; -import "@goauthentik/elements/forms/SearchSelect"; +import "@goauthentik/elements/forms/SearchSelect/ak-search-select"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; diff --git a/web/src/admin/applications/ApplicationListPage.ts b/web/src/admin/applications/ApplicationListPage.ts index da60282a18..5e5a70c2c0 100644 --- a/web/src/admin/applications/ApplicationListPage.ts +++ b/web/src/admin/applications/ApplicationListPage.ts @@ -1,17 +1,17 @@ -import "@goauthentik/admin/applications/ApplicationForm"; -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import MDApplication from "@goauthentik/docs/add-secure-apps/applications/index.md"; -import "@goauthentik/elements/AppIcon.js"; -import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; -import "@goauthentik/elements/ak-mdx"; -import "@goauthentik/elements/buttons/SpinnerButton"; -import "@goauthentik/elements/forms/DeleteBulkForm"; -import "@goauthentik/elements/forms/ModalForm"; -import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; -import { PaginatedResponse } from "@goauthentik/elements/table/Table"; -import { TableColumn } from "@goauthentik/elements/table/Table"; -import { TablePage } from "@goauthentik/elements/table/TablePage"; +import "#admin/applications/ApplicationForm"; +import { DEFAULT_CONFIG } from "#common/api/config"; +import "#elements/AppIcon"; +import "#elements/ak-mdx/ak-mdx"; +import "#elements/buttons/SpinnerButton/ak-spinner-button"; +import "#elements/forms/DeleteBulkForm"; +import "#elements/forms/ModalForm"; +import { WithBrandConfig } from "#elements/mixins/branding"; +import { getURLParam } from "#elements/router/RouteMatch"; +import { PaginatedResponse } from "#elements/table/Table"; +import { TableColumn } from "#elements/table/Table"; +import { TablePage } from "#elements/table/TablePage"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; +import MDApplication from "~docs/add-secure-apps/applications/index.md"; import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -22,7 +22,7 @@ import PFCard from "@patternfly/patternfly/components/Card/card.css"; import { Application, CoreApi, PoliciesApi } from "@goauthentik/api"; -import "./ApplicationWizardHint"; +import "./ApplicationWizardHint.js"; export const applicationListStyle = css` /* Fix alignment issues with images in tables */ @@ -50,7 +50,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage) } pageDescription(): string { return msg( - str`External applications that use ${this.brand?.brandingTitle ?? "authentik"} as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.`, + str`External applications that use ${this.brandingTitle} as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.`, ); } pageIcon(): string { diff --git a/web/src/admin/applications/wizard/ContextIdentity.ts b/web/src/admin/applications/wizard/ContextIdentity.ts index 260ea726f9..a32d7a8344 100644 --- a/web/src/admin/applications/wizard/ContextIdentity.ts +++ b/web/src/admin/applications/wizard/ContextIdentity.ts @@ -3,5 +3,5 @@ import { createContext } from "@lit/context"; import { LocalTypeCreate } from "./steps/ProviderChoices.js"; export const applicationWizardProvidersContext = createContext( - Symbol("ak-application-wizard-providers-context"), + Symbol.for("ak-application-wizard-providers-context"), ); diff --git a/web/src/admin/applications/wizard/steps/ak-application-wizard-provider-choice-step.ts b/web/src/admin/applications/wizard/steps/ak-application-wizard-provider-choice-step.ts index 140a0f9b96..cec9695126 100644 --- a/web/src/admin/applications/wizard/steps/ak-application-wizard-provider-choice-step.ts +++ b/web/src/admin/applications/wizard/steps/ak-application-wizard-provider-choice-step.ts @@ -1,8 +1,8 @@ +import { WithLicenseSummary } from "#elements/mixins/license"; import { ApplicationWizardStep } from "@goauthentik/admin/applications/wizard/ApplicationWizardStep.js"; import "@goauthentik/admin/applications/wizard/ak-wizard-title.js"; import type { NavigableButton, WizardButton } from "@goauthentik/components/ak-wizard/types"; import "@goauthentik/elements/EmptyState.js"; -import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js"; import { bound } from "@goauthentik/elements/decorators/bound.js"; import "@goauthentik/elements/forms/FormGroup.js"; import "@goauthentik/elements/forms/HorizontalFormElement.js"; diff --git a/web/src/admin/applications/wizard/steps/providers/ak-application-wizard-provider-for-ldap.ts b/web/src/admin/applications/wizard/steps/providers/ak-application-wizard-provider-for-ldap.ts index b83c3ab3d7..fbde2a86cb 100644 --- a/web/src/admin/applications/wizard/steps/providers/ak-application-wizard-provider-for-ldap.ts +++ b/web/src/admin/applications/wizard/steps/providers/ak-application-wizard-provider-for-ldap.ts @@ -1,7 +1,7 @@ +import { WithBrandConfig } from "#elements/mixins/branding"; import "@goauthentik/admin/applications/wizard/ak-wizard-title.js"; import { ValidationRecord } from "@goauthentik/admin/applications/wizard/types"; import { renderForm } from "@goauthentik/admin/providers/ldap/LDAPProviderFormForm.js"; -import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider.js"; import { msg } from "@lit/localize"; import { html } from "lit"; diff --git a/web/src/admin/applications/wizard/steps/providers/ak-application-wizard-provider-for-radius.ts b/web/src/admin/applications/wizard/steps/providers/ak-application-wizard-provider-for-radius.ts index bac20f2f19..5c045a2132 100644 --- a/web/src/admin/applications/wizard/steps/providers/ak-application-wizard-provider-for-radius.ts +++ b/web/src/admin/applications/wizard/steps/providers/ak-application-wizard-provider-for-radius.ts @@ -1,7 +1,7 @@ +import { WithBrandConfig } from "#elements/mixins/branding"; import "@goauthentik/admin/applications/wizard/ak-wizard-title.js"; import { ValidationRecord } from "@goauthentik/admin/applications/wizard/types"; import { renderForm } from "@goauthentik/admin/providers/radius/RadiusProviderFormForm.js"; -import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import { msg } from "@lit/localize"; import { customElement } from "@lit/reactive-element/decorators.js"; diff --git a/web/src/admin/common/ak-license-notice.ts b/web/src/admin/common/ak-license-notice.ts index 117d2852ad..2fbc15710a 100644 --- a/web/src/admin/common/ak-license-notice.ts +++ b/web/src/admin/common/ak-license-notice.ts @@ -1,6 +1,6 @@ +import { WithLicenseSummary } from "#elements/mixins/license"; import "@goauthentik/elements/Alert"; import { AKElement } from "@goauthentik/elements/Base"; -import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider"; import { msg } from "@lit/localize"; import { html, nothing } from "lit"; diff --git a/web/src/admin/flows/FlowForm.ts b/web/src/admin/flows/FlowForm.ts index 1696f07cfe..35dac2554b 100644 --- a/web/src/admin/flows/FlowForm.ts +++ b/web/src/admin/flows/FlowForm.ts @@ -1,10 +1,7 @@ +import { CapabilitiesEnum, WithCapabilitiesConfig } from "#elements/mixins/capabilities"; import { DesignationToLabel, LayoutToLabel } from "@goauthentik/admin/flows/utils"; import { AuthenticationEnum } from "@goauthentik/api/dist/models/AuthenticationEnum"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { - CapabilitiesEnum, - WithCapabilitiesConfig, -} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/HorizontalFormElement"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; diff --git a/web/src/admin/groups/RelatedUserList.ts b/web/src/admin/groups/RelatedUserList.ts index 3942c7a98c..8c4b151625 100644 --- a/web/src/admin/groups/RelatedUserList.ts +++ b/web/src/admin/groups/RelatedUserList.ts @@ -1,3 +1,5 @@ +import { WithBrandConfig } from "#elements/mixins/branding"; +import { CapabilitiesEnum, WithCapabilitiesConfig } from "#elements/mixins/capabilities"; import "@goauthentik/admin/users/ServiceAccountForm"; import "@goauthentik/admin/users/UserActiveForm"; import "@goauthentik/admin/users/UserForm"; @@ -11,11 +13,6 @@ import { MessageLevel } from "@goauthentik/common/messages"; import { formatElapsedTime } from "@goauthentik/common/temporal"; import { me } from "@goauthentik/common/users"; import "@goauthentik/components/ak-status-label"; -import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; -import { - CapabilitiesEnum, - WithCapabilitiesConfig, -} from "@goauthentik/elements/Interface/capabilitiesProvider"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/forms/DeleteBulkForm"; @@ -295,7 +292,7 @@ export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Tabl ${msg("Set password")} - ${this.brand?.flowRecovery + ${this.brand.flowRecovery ? html` [HTTPStatusCode.Forbidden]: GenericErrorFromJSON, } as const; +//#endregion + +//#region Type Predicates + /** - * Type guard to check if a response contains a JSON body. + * Type predicate to check if a response contains a JSON body. * * This is useful to guard against parsing errors when attempting to read the response body. */ @@ -35,6 +39,24 @@ export function isJSONResponse(response: Response): boolean { return Boolean(response.headers.get("content-type")?.includes("application/json")); } +/** + * An error originating from an aborted request. + * + * @see {@linkcode isAbortError} to check if an error originates from an aborted request. + */ +export interface AbortErrorLike extends DOMException { + name: "AbortError"; +} + +/** + * Type predicate to check if an error originates from an aborted request. + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort | MDN} + */ +export function isAbortError(error: unknown): error is AbortErrorLike { + return error instanceof DOMException && error.name === "AbortError"; +} + //#endregion //#region API diff --git a/web/src/common/theme.ts b/web/src/common/theme.ts index bb105f5d16..1d9b66599a 100644 --- a/web/src/common/theme.ts +++ b/web/src/common/theme.ts @@ -2,13 +2,12 @@ * @file Theme utilities. */ import { type StyleRoot, createStyleSheetUnsafe, setAdoptedStyleSheets } from "#common/stylesheets"; -import { UIConfig } from "#common/ui/config"; import AKBase from "#common/styles/authentik.css"; import AKBaseDark from "#common/styles/theme-dark.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { Config, CurrentBrand, UiThemeEnum } from "@goauthentik/api"; +import { UiThemeEnum } from "@goauthentik/api"; //#region Stylesheet Exports @@ -259,6 +258,8 @@ export function applyUITheme( export function applyDocumentTheme(hint: CSSColorSchemeValue | UIThemeHint = "auto"): void { const preferredColorScheme = formatColorScheme(hint); + if (document.documentElement.dataset.theme === preferredColorScheme) return; + const applyStyleSheets: UIThemeListener = (currentUITheme) => { console.debug(`authentik/theme (document): switching to ${currentUITheme} theme`); @@ -285,36 +286,20 @@ export function applyDocumentTheme(hint: CSSColorSchemeValue | UIThemeHint = "au applyStyleSheets(preferredColorScheme); } -/** - * An element that can be themed. - */ -export interface ThemedElement extends HTMLElement { - /** - * The brand information for the current theme. - */ - readonly brand?: CurrentBrand; - /** - * The UI configuration for the current theme, - * typically injected through a Lit Mixin. - * - * @see {@linkcode UIConfig} for details. - */ - readonly uiConfig?: UIConfig; - /** - * An authentik configuration initially provided by the server. - */ - readonly config?: Config; - activeTheme: ResolvedUITheme; -} - /** * Returns the root interface element of the page. * * @todo Can this be handled with a Lit Mixin? */ -export function rootInterface(): T | null { +export function rootInterface(): T { const element = document.body.querySelector("[data-ak-interface-root]"); + if (!element) { + throw new Error( + `Could not find root interface element. Was this element added before the parent interface element?`, + ); + } + return element; } diff --git a/web/src/components/ak-page-navbar.ts b/web/src/components/ak-page-navbar.ts index 63856fcfc3..99a519f8b5 100644 --- a/web/src/components/ak-page-navbar.ts +++ b/web/src/components/ak-page-navbar.ts @@ -1,12 +1,11 @@ -import { EVENT_WS_MESSAGE, TITLE_DEFAULT } from "#common/constants"; +import { EVENT_WS_MESSAGE } from "#common/constants"; import { globalAK } from "#common/global"; import { UIConfig, UserDisplay, getConfigForUser } from "#common/ui/config"; -import { DefaultBrand } from "#common/ui/config"; import { me } from "#common/users"; import "#components/ak-nav-buttons"; import type { PageHeaderInit, SidebarToggleEventDetail } from "#components/ak-page-header"; import { AKElement } from "#elements/Base"; -import { WithBrandConfig } from "#elements/Interface/brandProvider"; +import { WithBrandConfig } from "#elements/mixins/branding"; import { isAdminRoute } from "#elements/router/utils"; import { themeImage } from "#elements/utils/images"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; @@ -290,7 +289,7 @@ export class AKPageNavbar //#region Private Methods #setTitle(header?: string) { - let title = this.brand?.brandingTitle || TITLE_DEFAULT; + let title = this.brandingTitle; if (isAdminRoute()) { title = `${msg("Admin")} - ${title}`; @@ -368,9 +367,7 @@ export class AKPageNavbar @@ -531,7 +527,7 @@ export class FlowExecutor extends Interface implements StageHost { diff --git a/web/src/flow/stages/prompt/PromptStage.ts b/web/src/flow/stages/prompt/PromptStage.ts index cef4997f6b..629b2c3d8b 100644 --- a/web/src/flow/stages/prompt/PromptStage.ts +++ b/web/src/flow/stages/prompt/PromptStage.ts @@ -1,9 +1,6 @@ +import { CapabilitiesEnum, WithCapabilitiesConfig } from "#elements/mixins/capabilities"; import "@goauthentik/elements/Divider"; import "@goauthentik/elements/EmptyState"; -import { - CapabilitiesEnum, - WithCapabilitiesConfig, -} from "@goauthentik/elements/Interface/capabilitiesProvider"; import { LOCALES } from "@goauthentik/elements/ak-locale-context/definitions"; import "@goauthentik/elements/forms/FormElement"; import { BaseStage } from "@goauthentik/flow/stages/base"; diff --git a/web/src/rac/index.entrypoint.ts b/web/src/rac/index.entrypoint.ts index e8c297bbaa..161e5eafab 100644 --- a/web/src/rac/index.entrypoint.ts +++ b/web/src/rac/index.entrypoint.ts @@ -1,13 +1,13 @@ -import { TITLE_DEFAULT } from "@goauthentik/common/constants"; -import { Interface } from "@goauthentik/elements/Interface"; -import "@goauthentik/elements/LoadingOverlay"; +import { Interface } from "#elements/Interface"; +import "#elements/LoadingOverlay"; +import { WithBrandConfig } from "#elements/mixins/branding"; import Guacamole from "guacamole-common-js"; import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; -import AKGlobal from "@goauthentik/common/styles/authentik.css"; +import AKGlobal from "#common/styles/authentik.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; @@ -43,7 +43,7 @@ const RECONNECT_ATTEMPTS_INITIAL = 5; const RECONNECT_ATTEMPTS = 5; @customElement("ak-rac") -export class RacInterface extends Interface { +export class RacInterface extends WithBrandConfig(Interface) { static get styles(): CSSResult[] { return [ PFBase, @@ -231,10 +231,12 @@ export class RacInterface extends Interface { } updateTitle(): void { - let title = this.brand?.brandingTitle || TITLE_DEFAULT; + let title = this.brandingTitle; + if (this.endpointName) { title = `${this.endpointName} - ${title}`; } + document.title = `${title}`; } diff --git a/web/src/standalone/api-browser/index.entrypoint.ts b/web/src/standalone/api-browser/index.entrypoint.ts index 440a5fdc8b..4d8956a0db 100644 --- a/web/src/standalone/api-browser/index.entrypoint.ts +++ b/web/src/standalone/api-browser/index.entrypoint.ts @@ -1,13 +1,13 @@ // sort-imports-ignore import "rapidoc"; -import "@goauthentik/elements/ak-locale-context/index.js"; +import "#elements/ak-locale-context/index"; -import { CSRFHeaderName } from "@goauthentik/common/api/middleware.js"; -import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants.js"; -import { getCookie } from "@goauthentik/common/utils.js"; -import { Interface } from "@goauthentik/elements/Interface/Interface.js"; -import { DefaultBrand } from "@goauthentik/common/ui/config.js"; -import { themeImage } from "@goauthentik/elements/utils/images.js"; +import { CSRFHeaderName } from "#common/api/middleware"; +import { EVENT_THEME_CHANGE } from "#common/constants"; +import { getCookie } from "#common/utils"; +import { Interface } from "#elements/Interface"; +import { WithBrandConfig } from "#elements/mixins/branding"; +import { themeImage } from "#elements/utils/images"; import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, css, html } from "lit"; @@ -17,7 +17,7 @@ import { ifDefined } from "lit/directives/if-defined.js"; import { UiThemeEnum } from "@goauthentik/api"; @customElement("ak-api-browser") -export class APIBrowser extends Interface { +export class APIBrowser extends WithBrandConfig(Interface) { @property() schemaPath?: string; @@ -102,9 +102,7 @@ export class APIBrowser extends Interface { diff --git a/web/src/standalone/loading/index.entrypoint.ts b/web/src/standalone/loading/index.entrypoint.ts index 0748c5638c..d5212826da 100644 --- a/web/src/standalone/loading/index.entrypoint.ts +++ b/web/src/standalone/loading/index.entrypoint.ts @@ -1,4 +1,6 @@ -import { LightInterface } from "@goauthentik/elements/Interface"; +import { globalAK } from "#common/global"; +import { applyDocumentTheme } from "#common/theme"; +import { AKElement } from "#elements/Base"; import { msg } from "@lit/localize"; import { TemplateResult, css, html } from "lit"; @@ -10,7 +12,7 @@ import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; @customElement("ak-loading") -export class Loading extends LightInterface { +export class Loading extends AKElement { static styles = [ PFBase, PFPage, @@ -23,6 +25,16 @@ export class Loading extends LightInterface { `, ]; + constructor() { + super(); + + applyDocumentTheme(globalAK().brand.uiTheme); + } + + public connectedCallback(): void { + this.dataset.akInterfaceRoot = this.tagName.toLowerCase(); + } + render(): TemplateResult { return html`
()?.me; + const { me, uiConfig } = rootInterface(); return html`
${application.metaPublisher}
${truncateWords(application.metaDescription || "", 10)} - ${rootInterface()?.uiConfig?.enabledFeatures.applicationEdit && me?.user.isSuperuser + ${uiConfig?.enabledFeatures.applicationEdit && me?.user.isSuperuser ? html`
`; } - const me = rootInterface()?.me; + const { me, uiConfig } = rootInterface(); + const expandable = - (rootInterface()?.uiConfig?.enabledFeatures.applicationEdit && me?.user.isSuperuser) || + (uiConfig?.enabledFeatures.applicationEdit && me?.user.isSuperuser) || this.application.metaPublisher !== "" || this.application.metaDescription !== ""; diff --git a/web/src/user/LibraryPage/ak-library.ts b/web/src/user/LibraryPage/ak-library.ts index 378826089d..458d3cc731 100644 --- a/web/src/user/LibraryPage/ak-library.ts +++ b/web/src/user/LibraryPage/ak-library.ts @@ -1,7 +1,9 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { rootInterface } from "@goauthentik/common/theme"; import { me } from "@goauthentik/common/users"; -import { AKElement, rootInterface } from "@goauthentik/elements/Base"; +import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/EmptyState"; +import type { UserInterface } from "@goauthentik/user/index.entrypoint"; import { localized, msg } from "@lit/localize"; import { html } from "lit"; @@ -47,7 +49,8 @@ export class LibraryPage extends AKElement { constructor() { super(); - const uiConfig = rootInterface()?.uiConfig; + const { uiConfig } = rootInterface(); + if (!uiConfig) { throw new Error("Could not retrieve uiConfig. Reason: unknown. Check logs."); } diff --git a/web/src/user/index.entrypoint.ts b/web/src/user/index.entrypoint.ts index 5bc8721fe4..29dd0ea10b 100644 --- a/web/src/user/index.entrypoint.ts +++ b/web/src/user/index.entrypoint.ts @@ -1,30 +1,31 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { DEFAULT_CONFIG } from "#common/api/config"; import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_WS_MESSAGE, -} from "@goauthentik/common/constants"; -import { globalAK } from "@goauthentik/common/global"; -import { configureSentry } from "@goauthentik/common/sentry"; -import { UIConfig, getConfigForUser } from "@goauthentik/common/ui/config"; -import { DefaultBrand } from "@goauthentik/common/ui/config"; -import { me } from "@goauthentik/common/users"; -import { WebsocketClient } from "@goauthentik/common/ws"; -import "@goauthentik/components/ak-nav-buttons"; -import { AKElement } from "@goauthentik/elements/Base"; -import { AuthenticatedInterface } from "@goauthentik/elements/Interface"; -import "@goauthentik/elements/ak-locale-context"; -import "@goauthentik/elements/banner/EnterpriseStatusBanner"; -import "@goauthentik/elements/buttons/ActionButton"; -import "@goauthentik/elements/messages/MessageContainer"; -import "@goauthentik/elements/notifications/APIDrawer"; -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 { themeImage } from "@goauthentik/elements/utils/images"; -import { ROUTES } from "@goauthentik/user/Routes"; +} from "#common/constants"; +import { globalAK } from "#common/global"; +import { configureSentry } from "#common/sentry/index"; +import { UIConfig, getConfigForUser } from "#common/ui/config"; +import { DefaultBrand } from "#common/ui/config"; +import { me } from "#common/users"; +import { WebsocketClient } from "#common/ws"; +import "#components/ak-nav-buttons"; +import { AuthenticatedInterface } from "#elements/AuthenticatedInterface"; +import { AKElement } from "#elements/Base"; +import "#elements/ak-locale-context/ak-locale-context"; +import "#elements/banner/EnterpriseStatusBanner"; +import "#elements/buttons/ActionButton/ak-action-button"; +import "#elements/messages/MessageContainer"; +import { WithBrandConfig } from "#elements/mixins/branding"; +import "#elements/notifications/APIDrawer"; +import "#elements/notifications/NotificationDrawer"; +import { getURLParam, updateURLParams } from "#elements/router/RouteMatch"; +import "#elements/router/RouterOutlet"; +import "#elements/sidebar/Sidebar"; +import "#elements/sidebar/SidebarItem"; +import { themeImage } from "#elements/utils/images"; +import { ROUTES } from "#user/Routes"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; @@ -41,7 +42,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; -import { CurrentBrand, EventsApi, SessionUser } from "@goauthentik/api"; +import { EventsApi, SessionUser } from "@goauthentik/api"; if (process.env.NODE_ENV === "development") { await import("@goauthentik/esbuild-plugin-live-reload/client"); @@ -117,21 +118,19 @@ const customStyles = css` @customElement("ak-interface-user-presentation") // @ts-ignore -class UserInterfacePresentation extends AKElement { - static get styles() { - return [ - PFBase, - PFDisplay, - PFBrand, - PFPage, - PFAvatar, - PFButton, - PFDrawer, - PFDropdown, - PFNotificationBadge, - customStyles, - ]; - } +class UserInterfacePresentation extends WithBrandConfig(AKElement) { + static styles = [ + PFBase, + PFDisplay, + PFBrand, + PFPage, + PFAvatar, + PFButton, + PFDrawer, + PFDropdown, + PFNotificationBadge, + customStyles, + ]; @property({ type: Object }) uiConfig!: UIConfig; @@ -148,9 +147,6 @@ class UserInterfacePresentation extends AKElement { @property({ type: Number }) notificationsCount = 0; - @property({ type: Object }) - brand!: CurrentBrand; - get canAccessAdmin() { return ( this.me.user.isSuperuser || @@ -206,8 +202,8 @@ class UserInterfacePresentation extends AKElement { ${this.brand.brandingTitle} @@ -265,7 +261,7 @@ class UserInterfacePresentation extends AKElement { // // @customElement("ak-interface-user") -export class UserInterface extends AuthenticatedInterface { +export class UserInterface extends WithBrandConfig(AuthenticatedInterface) { @property({ type: Boolean }) notificationDrawerOpen = getURLParam("notificationDrawerOpen", false); @@ -278,7 +274,10 @@ export class UserInterface extends AuthenticatedInterface { notificationsCount = 0; @state() - me?: SessionUser; + me: SessionUser | null = null; + + @state() + uiConfig: UIConfig | null = null; constructor() { configureSentry(true); diff --git a/web/src/user/user-settings/UserSettingsPage.ts b/web/src/user/user-settings/UserSettingsPage.ts index 038cfcf3b1..126bbd2330 100644 --- a/web/src/user/user-settings/UserSettingsPage.ts +++ b/web/src/user/user-settings/UserSettingsPage.ts @@ -1,6 +1,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; -import { AKElement, rootInterface } from "@goauthentik/elements/Base"; +import { rootInterface } from "@goauthentik/common/theme"; +import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/user/SessionList"; import "@goauthentik/elements/user/UserConsentList"; diff --git a/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts b/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts index c8bdbc6e40..9db2cbed4f 100644 --- a/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts +++ b/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts @@ -1,18 +1,14 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { EVENT_REFRESH } from "@goauthentik/common/constants"; -import { - APIError, - parseAPIResponseError, - pluckErrorDetail, -} from "@goauthentik/common/errors/network"; -import { globalAK } from "@goauthentik/common/global"; -import { MessageLevel } from "@goauthentik/common/messages"; -import { refreshMe } from "@goauthentik/common/users"; -import { AKElement } from "@goauthentik/elements/Base"; -import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; -import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; -import { StageHost } from "@goauthentik/flow/stages/base"; -import "@goauthentik/user/user-settings/details/stages/prompt/PromptStage"; +import { DEFAULT_CONFIG } from "#common/api/config"; +import { EVENT_REFRESH } from "#common/constants"; +import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network"; +import { globalAK } from "#common/global"; +import { MessageLevel } from "#common/messages"; +import { refreshMe } from "#common/users"; +import { AKElement } from "#elements/Base"; +import { showMessage } from "#elements/messages/MessageContainer"; +import { WithBrandConfig } from "#elements/mixins/branding"; +import { StageHost } from "#flow/stages/base"; +import "#user/user-settings/details/stages/prompt/PromptStage"; import { msg } from "@lit/localize"; import { CSSResult, PropertyValues, TemplateResult, html } from "lit"; @@ -92,10 +88,10 @@ export class UserSettingsFlowExecutor updated(changedProperties: PropertyValues): void { if (changedProperties.has("brand") && this.brand) { - this.flowSlug = this.brand?.flowUserSettings; - if (!this.flowSlug) { - return; - } + this.flowSlug = this.brand.flowUserSettings; + + if (!this.flowSlug) return; + this.nextChallenge(); } } diff --git a/web/tsconfig.json b/web/tsconfig.json index 625791ce26..7bfa564b86 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -21,7 +21,6 @@ "@goauthentik/admin/*": ["./src/admin/*"], "@goauthentik/common/*": ["./src/common/*"], "@goauthentik/components/*": ["./src/components/*"], - "@goauthentik/docs/*": ["../website/docs/*"], "@goauthentik/elements/*": ["./src/elements/*"], "@goauthentik/flow/*": ["./src/flow/*"], "@goauthentik/locales/*": ["./src/locales/*"], diff --git a/web/tsconfig.test.json b/web/tsconfig.test.json index 8ded1a98d4..f73716f892 100644 --- a/web/tsconfig.test.json +++ b/web/tsconfig.test.json @@ -24,7 +24,6 @@ "@goauthentik/admin/*": ["./src/admin/*"], "@goauthentik/common/*": ["./src/common/*"], "@goauthentik/components/*": ["./src/components/*"], - "@goauthentik/docs/*": ["../website/docs/*"], "@goauthentik/elements/*": ["./src/elements/*"], "@goauthentik/flow/*": ["./src/flow/*"], "@goauthentik/locales/*": ["./src/locales/*"], diff --git a/web/types/mdx.d.ts b/web/types/mdx.d.ts index 42ecc39781..f147e3b4cf 100644 --- a/web/types/mdx.d.ts +++ b/web/types/mdx.d.ts @@ -1,4 +1,28 @@ -declare module "*.md" { +/** + * @file Provides types for ESBuild "virtual modules" generated from MDX files. + */ + +declare module "~docs/types" { + /** + * A parsed JSON module containing MDX content and metadata from ESBuild. + */ + export interface MDXModule { + /** + * The Markdown content of the module. + */ + content: string; + /** + * The public path of the module, typically identical to the docs page path. + */ + publicPath?: string; + /** + * The public directory of the module, used to resolve relative links. + */ + publicDirectory?: string; + } +} + +declare module "~docs/*.md" { /** * The serialized JSON content of an MD file. */ @@ -6,7 +30,7 @@ declare module "*.md" { export default serializedJSON; } -declare module "*.mdx" { +declare module "~docs/*.mdx" { /** * The serialized JSON content of an MDX file. */