web: Update WebDriver types. Fix issues surrounding async tests.
WIP; WIP 2 web: Flesh out fixtures, test IDs. web: Flesh out provider tests. web: Flesh out LDAP test. web: Fix typo. web: Allow base URL to be updated. web: Clean up. web: Tidy types. web: Update ARIA attributes for better test targeting. web: Clean up message labeling. web: Clean up ARIA labels. web: Flesh out table ARIA labels. web: Flesh out series. web: Fix linter. web: Clean up test reporting, timing issues. Add RADIUS test.
This commit is contained in:
@ -141,6 +141,7 @@ skip = [
|
|||||||
"**/web/src/locales",
|
"**/web/src/locales",
|
||||||
"**/web/xliff",
|
"**/web/xliff",
|
||||||
"**/web/out",
|
"**/web/out",
|
||||||
|
"**/web/playwright-report",
|
||||||
"./web/storybook-static",
|
"./web/storybook-static",
|
||||||
"./web/custom-elements.json",
|
"./web/custom-elements.json",
|
||||||
"./website/build",
|
"./website/build",
|
||||||
|
|||||||
2
web/.gitignore
vendored
2
web/.gitignore
vendored
@ -25,6 +25,8 @@ lib-cov
|
|||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
coverage
|
coverage
|
||||||
|
playwright-report
|
||||||
|
test-results
|
||||||
*.lcov
|
*.lcov
|
||||||
|
|
||||||
# nyc test coverage
|
# nyc test coverage
|
||||||
|
|||||||
@ -27,11 +27,6 @@ const inlineCSSPlugin = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @satisfies {InlineConfig}
|
|
||||||
*/
|
|
||||||
// const viteFinal = ;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @satisfies {StorybookConfig}
|
* @satisfies {StorybookConfig}
|
||||||
*/
|
*/
|
||||||
|
|||||||
89
web/e2e/elements/proxy.ts
Normal file
89
web/e2e/elements/proxy.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import type { LocatorContext } from "#e2e/selectors/types";
|
||||||
|
import { ConsoleLogger } from "#logger/node";
|
||||||
|
import { Locator, Page, expect } from "@playwright/test";
|
||||||
|
import { kebabCase } from "change-case";
|
||||||
|
|
||||||
|
export type LocatorMatchers = ReturnType<typeof expect<Locator>>;
|
||||||
|
|
||||||
|
export interface LocatorProxy extends Pick<Locator, keyof Locator> {
|
||||||
|
$: Locator;
|
||||||
|
expect: LocatorMatchers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type helpers to extract the shape of the proxy
|
||||||
|
export type DeepLocatorProxy<T> =
|
||||||
|
Disposable & T extends Record<string, any>
|
||||||
|
? T extends HTMLElement
|
||||||
|
? LocatorProxy
|
||||||
|
: {
|
||||||
|
[K in keyof T]: DeepLocatorProxy<T[K]>;
|
||||||
|
}
|
||||||
|
: LocatorProxy;
|
||||||
|
|
||||||
|
export function createLocatorProxy<T extends Record<string, any>>(
|
||||||
|
ctx: LocatorContext,
|
||||||
|
initialPathPrefix: string[] = [],
|
||||||
|
dataAttribute: string = "test-id",
|
||||||
|
): DeepLocatorProxy<T> {
|
||||||
|
dataAttribute = kebabCase(dataAttribute);
|
||||||
|
|
||||||
|
function createProxy(path: string[] = initialPathPrefix): any {
|
||||||
|
const proxyCache = new Map<string, LocatorProxy>();
|
||||||
|
|
||||||
|
return new Proxy({} as any, {
|
||||||
|
get(_, property: string) {
|
||||||
|
// Build the current path
|
||||||
|
const currentPath = [...path, property];
|
||||||
|
|
||||||
|
// Convert the path to kebab-case and join with hyphens
|
||||||
|
const selectorValue = currentPath.map((segment) => kebabCase(segment)).join("-");
|
||||||
|
const selector = `[data-${dataAttribute}="${selectorValue}"]`;
|
||||||
|
|
||||||
|
// Create a locator for the current selector
|
||||||
|
const locator = ctx.locator(selector);
|
||||||
|
|
||||||
|
if (proxyCache.has(selector)) {
|
||||||
|
ConsoleLogger.debug(`Using cached locator for ${selector}`);
|
||||||
|
return proxyCache.get(selector)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a new proxy that also behaves like a Locator
|
||||||
|
// This allows us to either continue chaining or use Locator methods
|
||||||
|
const nextProxy = new Proxy(locator, {
|
||||||
|
get(target, prop) {
|
||||||
|
if (typeof prop === "string") {
|
||||||
|
// The user is likely trying to access a property on the page.
|
||||||
|
if (prop === "$") {
|
||||||
|
return target as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop === "expect") {
|
||||||
|
return expect(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the property exists on the Locator, use it
|
||||||
|
if (prop in target) {
|
||||||
|
const value = (target as any)[prop];
|
||||||
|
// Bind methods to the locator instance
|
||||||
|
if (typeof value === "function") {
|
||||||
|
return value.bind(target);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
// Otherwise, continue building the path
|
||||||
|
|
||||||
|
return createProxy(currentPath)[prop];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
proxyCache.set(selector, nextProxy as LocatorProxy);
|
||||||
|
|
||||||
|
return nextProxy;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return createProxy() as DeepLocatorProxy<T>;
|
||||||
|
}
|
||||||
174
web/e2e/fixtures/FormFixture.ts
Normal file
174
web/e2e/fixtures/FormFixture.ts
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
||||||
|
import type { LocatorContext } from "#e2e/selectors/types";
|
||||||
|
import { Page, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
export class FormFixture extends PageFixture {
|
||||||
|
static fixtureName = "Form";
|
||||||
|
|
||||||
|
//#region Selector Methods
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Field Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value of a text input.
|
||||||
|
*
|
||||||
|
* @param fieldName The name of the form element.
|
||||||
|
* @param value the value to set.
|
||||||
|
*/
|
||||||
|
public fill = async (
|
||||||
|
fieldName: string,
|
||||||
|
value: string,
|
||||||
|
parent: LocatorContext = this.page,
|
||||||
|
): Promise<void> => {
|
||||||
|
const control = parent
|
||||||
|
.getByRole("textbox", {
|
||||||
|
name: fieldName,
|
||||||
|
})
|
||||||
|
.or(
|
||||||
|
parent.getByRole("spinbutton", {
|
||||||
|
name: fieldName,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
|
||||||
|
|
||||||
|
await control.fill(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value of a radio or checkbox input.
|
||||||
|
*
|
||||||
|
* @param fieldName The name of the form element.
|
||||||
|
* @param value the value to set.
|
||||||
|
*/
|
||||||
|
public setInputCheck = async (
|
||||||
|
fieldName: string,
|
||||||
|
value: boolean = true,
|
||||||
|
parent: LocatorContext = this.page,
|
||||||
|
): Promise<void> => {
|
||||||
|
const control = parent.locator("ak-switch-input", {
|
||||||
|
hasText: fieldName,
|
||||||
|
});
|
||||||
|
|
||||||
|
await control.scrollIntoViewIfNeeded();
|
||||||
|
|
||||||
|
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
|
||||||
|
|
||||||
|
const currentChecked = await control
|
||||||
|
.getAttribute("checked")
|
||||||
|
.then((value) => value !== null);
|
||||||
|
|
||||||
|
if (currentChecked === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await control.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value of a radio or checkbox input.
|
||||||
|
*
|
||||||
|
* @param fieldName The name of the form element.
|
||||||
|
* @param pattern the value to set.
|
||||||
|
*/
|
||||||
|
public setRadio = async (
|
||||||
|
groupName: string,
|
||||||
|
fieldName: string,
|
||||||
|
parent: LocatorContext = this.page,
|
||||||
|
): Promise<void> => {
|
||||||
|
const group = parent.getByRole("group", { name: groupName });
|
||||||
|
|
||||||
|
await expect(group, `Field "${groupName}" should be visible`).toBeVisible();
|
||||||
|
const control = parent.getByRole("radio", { name: fieldName });
|
||||||
|
|
||||||
|
await control.setChecked(true, {
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value of a search select input.
|
||||||
|
*
|
||||||
|
* @param fieldLabel The name of the search select element.
|
||||||
|
* @param pattern The text to match against the search select entry.
|
||||||
|
*/
|
||||||
|
public selectSearchValue = async (
|
||||||
|
fieldLabel: string,
|
||||||
|
pattern: string | RegExp,
|
||||||
|
parent: LocatorContext = this.page,
|
||||||
|
): Promise<void> => {
|
||||||
|
const control = parent.getByRole("textbox", { name: fieldLabel });
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
control,
|
||||||
|
`Search select control (${fieldLabel}) should be visible`,
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
const fieldName = await control.getAttribute("name");
|
||||||
|
|
||||||
|
if (!fieldName) {
|
||||||
|
throw new Error(`Unable to find name attribute on search select (${fieldLabel})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the search select input control and activate it.
|
||||||
|
await control.click();
|
||||||
|
|
||||||
|
const button = this.page
|
||||||
|
// ---
|
||||||
|
.locator(`div[data-managed-for*="${fieldName}"] button`, {
|
||||||
|
hasText: pattern,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!button) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to find an ak-search-select entry matching ${fieldLabel}:${pattern.toString()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await button.click();
|
||||||
|
await this.page.keyboard.press("Tab");
|
||||||
|
await control.blur();
|
||||||
|
};
|
||||||
|
|
||||||
|
public setFormGroup = async (
|
||||||
|
pattern: string | RegExp,
|
||||||
|
value: boolean = true,
|
||||||
|
parent: LocatorContext = this.page,
|
||||||
|
) => {
|
||||||
|
const control = parent
|
||||||
|
.locator("ak-form-group", {
|
||||||
|
hasText: pattern,
|
||||||
|
})
|
||||||
|
.first();
|
||||||
|
|
||||||
|
const currentOpen = await control.getAttribute("open").then((value) => value !== null);
|
||||||
|
|
||||||
|
if (currentOpen === value) {
|
||||||
|
this.logger.debug(`Form group ${pattern} is already ${value ? "open" : "closed"}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`Toggling form group ${pattern} to ${value ? "open" : "closed"}`);
|
||||||
|
|
||||||
|
await control.click();
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
await expect(control).toHaveAttribute("open");
|
||||||
|
} else {
|
||||||
|
await expect(control).not.toHaveAttribute("open");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Lifecycle
|
||||||
|
|
||||||
|
constructor(page: Page, testName: string) {
|
||||||
|
super({ page, testName });
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
29
web/e2e/fixtures/PageFixture.ts
Normal file
29
web/e2e/fixtures/PageFixture.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { ConsoleLogger, FixtureLogger } from "#logger/node";
|
||||||
|
import { Page } from "@playwright/test";
|
||||||
|
|
||||||
|
export interface PageFixtureOptions {
|
||||||
|
page: Page;
|
||||||
|
testName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class PageFixture {
|
||||||
|
/**
|
||||||
|
* The name of the fixture.
|
||||||
|
*
|
||||||
|
* Used for logging.
|
||||||
|
*/
|
||||||
|
static fixtureName: string;
|
||||||
|
|
||||||
|
protected readonly logger: FixtureLogger;
|
||||||
|
protected readonly page: Page;
|
||||||
|
protected readonly testName: string;
|
||||||
|
|
||||||
|
constructor({ page, testName }: PageFixtureOptions) {
|
||||||
|
this.page = page;
|
||||||
|
this.testName = testName;
|
||||||
|
|
||||||
|
const Constructor = this.constructor as typeof PageFixture;
|
||||||
|
|
||||||
|
this.logger = ConsoleLogger.fixture(Constructor.fixtureName, this.testName);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
web/e2e/fixtures/PointerFixture.ts
Normal file
41
web/e2e/fixtures/PointerFixture.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
||||||
|
import type { LocatorContext } from "#e2e/selectors/types";
|
||||||
|
import { Page } from "@playwright/test";
|
||||||
|
|
||||||
|
export type GetByRoleParameters = Parameters<Page["getByRole"]>;
|
||||||
|
export type ARIARole = GetByRoleParameters[0];
|
||||||
|
export type ARIAOptions = GetByRoleParameters[1];
|
||||||
|
|
||||||
|
export type ClickByName = (name: string) => Promise<void>;
|
||||||
|
export type ClickByRole = (
|
||||||
|
role: ARIARole,
|
||||||
|
options?: ARIAOptions,
|
||||||
|
context?: LocatorContext,
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
export class PointerFixture extends PageFixture {
|
||||||
|
public static fixtureName = "Pointer";
|
||||||
|
|
||||||
|
public click = (
|
||||||
|
name: string,
|
||||||
|
optionsOrRole?: ARIAOptions | ARIARole,
|
||||||
|
context: LocatorContext = this.page,
|
||||||
|
): Promise<void> => {
|
||||||
|
if (typeof optionsOrRole === "string") {
|
||||||
|
return context.getByRole(optionsOrRole, { name }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
...optionsOrRole,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
context
|
||||||
|
// ---
|
||||||
|
.getByRole("button", options)
|
||||||
|
.or(context.getByRole("link", options))
|
||||||
|
.click()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
118
web/e2e/fixtures/SessionFixture.ts
Normal file
118
web/e2e/fixtures/SessionFixture.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
||||||
|
import { Page, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
export const GOOD_USERNAME = "test-admin@goauthentik.io";
|
||||||
|
export const GOOD_PASSWORD = "test-runner";
|
||||||
|
|
||||||
|
export const BAD_USERNAME = "bad-username@bad-login.io";
|
||||||
|
export const BAD_PASSWORD = "-this-is-a-bad-password-";
|
||||||
|
|
||||||
|
export interface LoginInit {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
to?: URL | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SessionFixture extends PageFixture {
|
||||||
|
static fixtureName = "Session";
|
||||||
|
|
||||||
|
public static readonly pathname = "/if/flow/default-authentication-flow/";
|
||||||
|
|
||||||
|
//#region Selectors
|
||||||
|
|
||||||
|
public $identificationStage = this.page.locator("ak-stage-identification");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The username field on the login page.
|
||||||
|
*/
|
||||||
|
public $usernameField = this.$identificationStage.locator('input[name="uidField"]');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The button to continue with the login process,
|
||||||
|
* typically to the password flow stage.
|
||||||
|
*/
|
||||||
|
public $submitUsernameStageButton = this.$identificationStage.locator('button[type="submit"]');
|
||||||
|
|
||||||
|
public $passwordStage = this.page.locator("ak-stage-password");
|
||||||
|
public $passwordField = this.$passwordStage.locator('input[name="password"]');
|
||||||
|
/**
|
||||||
|
* The button to submit the the login flow,
|
||||||
|
* typically redirecting to the authenticated interface.
|
||||||
|
*/
|
||||||
|
public $submitPasswordStageButton = this.$passwordStage.locator('button[type="submit"]');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A possible authentication failure message.
|
||||||
|
*/
|
||||||
|
public $authFailureMessage = this.page.locator(".pf-m-error");
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
constructor(page: Page, testName: string) {
|
||||||
|
super({ page, testName });
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Specific interactions
|
||||||
|
|
||||||
|
public async submitUsernameStage(username: string) {
|
||||||
|
this.logger.info("Submitting username stage", username);
|
||||||
|
|
||||||
|
await this.$usernameField.fill(username);
|
||||||
|
|
||||||
|
await expect(this.$submitUsernameStageButton).toBeEnabled();
|
||||||
|
|
||||||
|
await this.$submitUsernameStageButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async submitPasswordStage(password: string) {
|
||||||
|
this.logger.info("Submitting password stage");
|
||||||
|
|
||||||
|
await this.$passwordField.fill(password);
|
||||||
|
|
||||||
|
await expect(this.$submitPasswordStageButton).toBeEnabled();
|
||||||
|
|
||||||
|
await this.$submitPasswordStageButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkAuthenticated = async (): Promise<boolean> => {
|
||||||
|
// TODO: Check if the user is authenticated via API
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log into the application.
|
||||||
|
*/
|
||||||
|
public async login({
|
||||||
|
username = GOOD_USERNAME,
|
||||||
|
password = GOOD_PASSWORD,
|
||||||
|
to = SessionFixture.pathname,
|
||||||
|
}: LoginInit = {}) {
|
||||||
|
this.logger.info("Logging in...");
|
||||||
|
|
||||||
|
const initialURL = new URL(this.page.url());
|
||||||
|
|
||||||
|
if (initialURL.pathname === SessionFixture.pathname) {
|
||||||
|
this.logger.info("Skipping navigation because we're already in a authentication flow");
|
||||||
|
} else {
|
||||||
|
await this.page.goto(to.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.submitUsernameStage(username);
|
||||||
|
|
||||||
|
await this.$passwordField.waitFor({ state: "visible" });
|
||||||
|
|
||||||
|
await this.submitPasswordStage(password);
|
||||||
|
|
||||||
|
const expectedPathname = typeof to === "string" ? to : to.pathname;
|
||||||
|
|
||||||
|
await this.page.waitForURL(`**${expectedPathname}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Navigation
|
||||||
|
|
||||||
|
public async toLoginPage() {
|
||||||
|
await this.page.goto(SessionFixture.pathname);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
web/e2e/index.ts
Normal file
55
web/e2e/index.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
|
import { DeepLocatorProxy, createLocatorProxy } from "#e2e/elements/proxy";
|
||||||
|
import { FormFixture } from "#e2e/fixtures/FormFixture";
|
||||||
|
import { PointerFixture } from "#e2e/fixtures/PointerFixture";
|
||||||
|
import { SessionFixture } from "#e2e/fixtures/SessionFixture";
|
||||||
|
import { createOUIDNameEngine } from "#e2e/selectors/ouid";
|
||||||
|
import { type Page, test as base } from "@playwright/test";
|
||||||
|
|
||||||
|
export { expect } from "@playwright/test";
|
||||||
|
|
||||||
|
type TestIDLocatorProxy = DeepLocatorProxy<TestIDSelectorMap>;
|
||||||
|
|
||||||
|
interface E2EFixturesTestScope {
|
||||||
|
/**
|
||||||
|
* A proxy to retrieve elements by test ID.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const $button = $.button;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
$: TestIDLocatorProxy;
|
||||||
|
session: SessionFixture;
|
||||||
|
pointer: PointerFixture;
|
||||||
|
form: FormFixture;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface E2EWorkerScope {
|
||||||
|
selectorRegistration: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = base.extend<E2EFixturesTestScope, E2EWorkerScope>({
|
||||||
|
selectorRegistration: [
|
||||||
|
async ({ playwright }, use) => {
|
||||||
|
await playwright.selectors.register("ouid", createOUIDNameEngine);
|
||||||
|
await use();
|
||||||
|
},
|
||||||
|
{ auto: true, scope: "worker" },
|
||||||
|
],
|
||||||
|
|
||||||
|
$: async ({ page }, use) => {
|
||||||
|
await use(createLocatorProxy<TestIDSelectorMap>(page));
|
||||||
|
},
|
||||||
|
|
||||||
|
session: async ({ page }, use, { title }) => {
|
||||||
|
await use(new SessionFixture(page, title));
|
||||||
|
},
|
||||||
|
|
||||||
|
form: async ({ page }, use, { title }) => {
|
||||||
|
await use(new FormFixture(page, title));
|
||||||
|
},
|
||||||
|
|
||||||
|
pointer: async ({ page }, use, { title }) => {
|
||||||
|
await use(new PointerFixture({ page, testName: title }));
|
||||||
|
},
|
||||||
|
});
|
||||||
44
web/e2e/selectors/ouid.ts
Normal file
44
web/e2e/selectors/ouid.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
type SelectorRoot = Document | ShadowRoot;
|
||||||
|
|
||||||
|
export function createOUIDNameEngine() {
|
||||||
|
const attributeName = "data-ouid-component-name";
|
||||||
|
|
||||||
|
console.log("Creating OUID selector engine!!");
|
||||||
|
return {
|
||||||
|
// Returns all elements matching given selector in the root's subtree.
|
||||||
|
queryAll(scope: SelectorRoot, componentName: string) {
|
||||||
|
const result: Element[] = [];
|
||||||
|
|
||||||
|
const match = (element: Element) => {
|
||||||
|
const name = element.getAttribute(attributeName);
|
||||||
|
|
||||||
|
if (name === componentName) {
|
||||||
|
result.push(element);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = (root: Element | ShadowRoot | Document) => {
|
||||||
|
const shadows: ShadowRoot[] = [];
|
||||||
|
|
||||||
|
if ((root as Element).shadowRoot) {
|
||||||
|
shadows.push((root as Element).shadowRoot!);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const element of root.querySelectorAll("*")) {
|
||||||
|
match(element);
|
||||||
|
|
||||||
|
if (element.shadowRoot) {
|
||||||
|
shadows.push(element.shadowRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shadows.forEach(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
query(scope);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
13
web/e2e/selectors/types.ts
Normal file
13
web/e2e/selectors/types.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { Locator } from "@playwright/test";
|
||||||
|
|
||||||
|
export type LocatorContext = Pick<
|
||||||
|
Locator,
|
||||||
|
| "locator"
|
||||||
|
| "getByRole"
|
||||||
|
| "getByTestId"
|
||||||
|
| "getByText"
|
||||||
|
| "getByLabel"
|
||||||
|
| "getByAltText"
|
||||||
|
| "getByTitle"
|
||||||
|
| "getByPlaceholder"
|
||||||
|
>;
|
||||||
59
web/e2e/utils/generators.ts
Normal file
59
web/e2e/utils/generators.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { IDGenerator } from "@goauthentik/core/id";
|
||||||
|
import {
|
||||||
|
Config as NameConfig,
|
||||||
|
adjectives,
|
||||||
|
colors,
|
||||||
|
uniqueNamesGenerator,
|
||||||
|
} from "unique-names-generator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a dictionary of words, slice the dictionary to only include words that start with the given letter.
|
||||||
|
*/
|
||||||
|
export function alliterate(dictionary: string[], letter: string): string[] {
|
||||||
|
let firstIndex = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < dictionary.length; i++) {
|
||||||
|
if (dictionary[i][0] === letter) {
|
||||||
|
firstIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastIndex = firstIndex;
|
||||||
|
|
||||||
|
for (let i = firstIndex; i < dictionary.length; i++) {
|
||||||
|
if (dictionary[i][0] !== letter) {
|
||||||
|
lastIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary.slice(firstIndex, lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRandomName({
|
||||||
|
seed = IDGenerator.randomID(),
|
||||||
|
...config
|
||||||
|
}: Partial<NameConfig> = {}) {
|
||||||
|
const randomLetterIndex =
|
||||||
|
typeof seed === "number"
|
||||||
|
? seed
|
||||||
|
: Array.from(seed).reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
|
|
||||||
|
const letter = adjectives[randomLetterIndex % adjectives.length][0];
|
||||||
|
|
||||||
|
const availableAdjectives = alliterate(adjectives, letter);
|
||||||
|
|
||||||
|
const availableColors = alliterate(colors, letter);
|
||||||
|
|
||||||
|
const name = uniqueNamesGenerator({
|
||||||
|
dictionaries: [availableAdjectives, availableAdjectives, availableColors],
|
||||||
|
style: "capital",
|
||||||
|
separator: " ",
|
||||||
|
length: 3,
|
||||||
|
seed,
|
||||||
|
...config,
|
||||||
|
});
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
101
web/logger/node.js
Normal file
101
web/logger/node.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* Application logger.
|
||||||
|
*
|
||||||
|
* @import { LoggerOptions, Logger, Level, ChildLoggerOptions } from "pino"
|
||||||
|
* @import { PrettyOptions } from "pino-pretty"
|
||||||
|
*/
|
||||||
|
import { pino } from "pino";
|
||||||
|
|
||||||
|
//#region Constants
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default options for creating a Pino logger.
|
||||||
|
*
|
||||||
|
* @category Logger
|
||||||
|
* @satisfies {LoggerOptions<never, false>}
|
||||||
|
*/
|
||||||
|
export const DEFAULT_PINO_LOGGER_OPTIONS = {
|
||||||
|
enabled: true,
|
||||||
|
level: "info",
|
||||||
|
transport: {
|
||||||
|
target: "./transport.js",
|
||||||
|
options: /** @satisfies {PrettyOptions} */ ({
|
||||||
|
colorize: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Functions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the log level from the environment.
|
||||||
|
* @return {Level}
|
||||||
|
*/
|
||||||
|
export function readLogLevel() {
|
||||||
|
return process.env.AK_LOG_LEVEL || DEFAULT_PINO_LOGGER_OPTIONS.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Logger} FixtureLogger
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this {Logger}
|
||||||
|
* @param {string} fixtureName
|
||||||
|
* @param {string} [testName]
|
||||||
|
* @param {ChildLoggerOptions} [options]
|
||||||
|
* @returns {FixtureLogger}
|
||||||
|
*/
|
||||||
|
function createFixtureLogger(fixtureName, testName, options) {
|
||||||
|
return this.child(
|
||||||
|
{ name: fixtureName },
|
||||||
|
{
|
||||||
|
msgPrefix: `[${testName}] `,
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} CustomLoggerMethods
|
||||||
|
* @property {typeof createFixtureLogger} fixture
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Logger & CustomLoggerMethods} ConsoleLogger
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A singleton logger instance for Node.js.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { ConsoleLogger } from "#logger/node";
|
||||||
|
*
|
||||||
|
* ConsoleLogger.info("Hello, world!");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @runtime node
|
||||||
|
* @type {ConsoleLogger}
|
||||||
|
*/
|
||||||
|
export const ConsoleLogger = Object.assign(
|
||||||
|
pino({
|
||||||
|
...DEFAULT_PINO_LOGGER_OPTIONS,
|
||||||
|
level: readLogLevel(),
|
||||||
|
}),
|
||||||
|
{ fixture: createFixtureLogger },
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {ReturnType<ConsoleLogger['child']>} ChildConsoleLogger
|
||||||
|
*/
|
||||||
|
|
||||||
|
//#region Aliases
|
||||||
|
|
||||||
|
export const info = ConsoleLogger.info.bind(ConsoleLogger);
|
||||||
|
export const debug = ConsoleLogger.debug.bind(ConsoleLogger);
|
||||||
|
export const warn = ConsoleLogger.warn.bind(ConsoleLogger);
|
||||||
|
export const error = ConsoleLogger.error.bind(ConsoleLogger);
|
||||||
|
|
||||||
|
//#endregion
|
||||||
21
web/logger/transport.js
Normal file
21
web/logger/transport.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @file Pretty transport for Pino
|
||||||
|
*
|
||||||
|
* @import { PrettyOptions } from "pino-pretty"
|
||||||
|
*/
|
||||||
|
import PinoPretty from "pino-pretty";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {PrettyOptions} options
|
||||||
|
*/
|
||||||
|
function prettyTransporter(options) {
|
||||||
|
const pretty = PinoPretty({
|
||||||
|
...options,
|
||||||
|
ignore: "pid,hostname",
|
||||||
|
translateTime: "SYS:HH:MM:ss",
|
||||||
|
});
|
||||||
|
|
||||||
|
return pretty;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default prettyTransporter;
|
||||||
8555
web/package-lock.json
generated
8555
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -24,8 +24,8 @@
|
|||||||
"pseudolocalize": "node ./scripts/pseudolocalize.mjs",
|
"pseudolocalize": "node ./scripts/pseudolocalize.mjs",
|
||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
"storybook:build": "wireit",
|
"storybook:build": "wireit",
|
||||||
"test": "wireit",
|
"test": "vitest",
|
||||||
"test:e2e": "wireit",
|
"test:e2e": "playwright test",
|
||||||
"test:e2e:watch": "wireit",
|
"test:e2e:watch": "wireit",
|
||||||
"test:watch": "wireit",
|
"test:watch": "wireit",
|
||||||
"tsc": "wireit",
|
"tsc": "wireit",
|
||||||
@ -69,6 +69,9 @@
|
|||||||
"#flow/*": "./src/flow/*.js",
|
"#flow/*": "./src/flow/*.js",
|
||||||
"#locales/*": "./src/locales/*.js",
|
"#locales/*": "./src/locales/*.js",
|
||||||
"#stories/*": "./src/stories/*.js",
|
"#stories/*": "./src/stories/*.js",
|
||||||
|
"#tests/*": "./tests/*.js",
|
||||||
|
"#e2e": "./e2e/index.ts",
|
||||||
|
"#e2e/*": "./e2e/*.ts",
|
||||||
"#*/browser": {
|
"#*/browser": {
|
||||||
"types": "./out/*/browser.d.ts",
|
"types": "./out/*/browser.d.ts",
|
||||||
"import": "./*/browser.js"
|
"import": "./*/browser.js"
|
||||||
@ -93,7 +96,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.7.11",
|
"@formatjs/intl-listformat": "^7.7.11",
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@goauthentik/api": "^2025.6.2-1750112513",
|
"@goauthentik/api": "^2025.6.1-1749515784",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
"@lit/reactive-element": "^2.0.4",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
@ -102,9 +105,10 @@
|
|||||||
"@open-wc/lit-helpers": "^0.7.0",
|
"@open-wc/lit-helpers": "^0.7.0",
|
||||||
"@patternfly/elements": "^4.1.0",
|
"@patternfly/elements": "^4.1.0",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
"@sentry/browser": "^9.29.0",
|
"@sentry/browser": "^9.28.0",
|
||||||
"@spotlightjs/spotlight": "^3.0.0",
|
"@spotlightjs/spotlight": "^2.13.3",
|
||||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
|
"base64-js": "^1.5.1",
|
||||||
"change-case": "^5.4.4",
|
"change-case": "^5.4.4",
|
||||||
"chart.js": "^4.4.9",
|
"chart.js": "^4.4.9",
|
||||||
"chartjs-adapter-date-fns": "^3.0.0",
|
"chartjs-adapter-date-fns": "^3.0.0",
|
||||||
@ -120,7 +124,9 @@
|
|||||||
"hastscript": "^9.0.1",
|
"hastscript": "^9.0.1",
|
||||||
"lit": "^3.2.0",
|
"lit": "^3.2.0",
|
||||||
"md-front-matter": "^1.0.4",
|
"md-front-matter": "^1.0.4",
|
||||||
"mermaid": "^11.6.0",
|
"mermaid": "^11.4.1",
|
||||||
|
"pino": "^9.7.0",
|
||||||
|
"pino-pretty": "^13.0.0",
|
||||||
"rapidoc": "^9.3.8",
|
"rapidoc": "^9.3.8",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
@ -136,7 +142,6 @@
|
|||||||
"trusted-types": "^2.0.0",
|
"trusted-types": "^2.0.0",
|
||||||
"ts-pattern": "^5.7.1",
|
"ts-pattern": "^5.7.1",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
"webauthn-polyfills": "^0.1.7",
|
|
||||||
"webcomponent-qr-code": "^1.2.0",
|
"webcomponent-qr-code": "^1.2.0",
|
||||||
"yaml": "^2.8.0"
|
"yaml": "^2.8.0"
|
||||||
},
|
},
|
||||||
@ -149,6 +154,7 @@
|
|||||||
"@goauthentik/tsconfig": "^1.0.4",
|
"@goauthentik/tsconfig": "^1.0.4",
|
||||||
"@hcaptcha/types": "^1.0.4",
|
"@hcaptcha/types": "^1.0.4",
|
||||||
"@lit/localize-tools": "^0.8.0",
|
"@lit/localize-tools": "^0.8.0",
|
||||||
|
"@playwright/test": "^1.52.0",
|
||||||
"@storybook/addon-essentials": "^8.6.14",
|
"@storybook/addon-essentials": "^8.6.14",
|
||||||
"@storybook/addon-links": "^8.6.14",
|
"@storybook/addon-links": "^8.6.14",
|
||||||
"@storybook/blocks": "^8.6.12",
|
"@storybook/blocks": "^8.6.12",
|
||||||
@ -158,6 +164,7 @@
|
|||||||
"@storybook/test": "^8.6.14",
|
"@storybook/test": "^8.6.14",
|
||||||
"@storybook/web-components": "^8.6.14",
|
"@storybook/web-components": "^8.6.14",
|
||||||
"@storybook/web-components-vite": "^8.6.14",
|
"@storybook/web-components-vite": "^8.6.14",
|
||||||
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
"@types/chart.js": "^2.9.41",
|
"@types/chart.js": "^2.9.41",
|
||||||
"@types/codemirror": "^5.60.15",
|
"@types/codemirror": "^5.60.15",
|
||||||
@ -170,16 +177,14 @@
|
|||||||
"@types/react-dom": "^19.1.5",
|
"@types/react-dom": "^19.1.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
||||||
"@typescript-eslint/parser": "^8.8.0",
|
"@typescript-eslint/parser": "^8.8.0",
|
||||||
"@wdio/browser-runner": "9.15",
|
"@vitest/browser": "^3.2.0",
|
||||||
"@wdio/cli": "9.15",
|
"@wdio/cli": "^9.15.0",
|
||||||
"@wdio/spec-reporter": "^9.15.0",
|
"@wdio/spec-reporter": "^9.15.0",
|
||||||
"@web/test-runner": "^0.20.2",
|
"esbuild": "^0.25.4",
|
||||||
"chromedriver": "^136.0.3",
|
|
||||||
"esbuild": "^0.25.5",
|
|
||||||
"esbuild-plugin-copy": "^2.1.1",
|
"esbuild-plugin-copy": "^2.1.1",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
|
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.28.0",
|
||||||
"eslint-plugin-lit": "^2.1.1",
|
"eslint-plugin-lit": "^2.1.1",
|
||||||
"eslint-plugin-wc": "^3.0.1",
|
"eslint-plugin-wc": "^3.0.1",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
@ -187,16 +192,22 @@
|
|||||||
"knip": "^5.58.0",
|
"knip": "^5.58.0",
|
||||||
"lit-analyzer": "^2.0.3",
|
"lit-analyzer": "^2.0.3",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
"p-iteration": "^1.1.8",
|
||||||
|
"playwright": "^1.52.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"pseudolocale": "^2.1.0",
|
"pseudolocale": "^2.1.0",
|
||||||
"rollup-plugin-postcss-lit": "^2.2.0",
|
"rollup-plugin-postcss-lit": "^2.2.0",
|
||||||
"storybook": "^8.6.14",
|
"storybook": "^8.6.14",
|
||||||
"storybook-addon-mock": "^5.0.0",
|
"storybook-addon-mock": "^5.0.0",
|
||||||
"turnstile-types": "^1.2.3",
|
"turnstile-types": "^1.2.3",
|
||||||
|
"type-fest": "^4.41.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.34.1",
|
"typescript-eslint": "^8.34.0",
|
||||||
"vite-plugin-lit-css": "^2.0.0",
|
"unique-names-generator": "^4.7.1",
|
||||||
|
"vite": "^6.3.5",
|
||||||
|
"vite-plugin-lit-css": "^2.1.0",
|
||||||
"vite-tsconfig-paths": "^5.0.1",
|
"vite-tsconfig-paths": "^5.0.1",
|
||||||
|
"vitest": "^3.2.0",
|
||||||
"wireit": "^0.14.12"
|
"wireit": "^0.14.12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
@ -278,7 +289,7 @@
|
|||||||
"command": "lit-analyzer src"
|
"command": "lit-analyzer src"
|
||||||
},
|
},
|
||||||
"lint:types:tests": {
|
"lint:types:tests": {
|
||||||
"command": "tsc --noEmit -p ./tests"
|
"command": "tsc --noEmit -p tsconfig.test.json"
|
||||||
},
|
},
|
||||||
"lint:types": {
|
"lint:types": {
|
||||||
"command": "tsc -p .",
|
"command": "tsc -p .",
|
||||||
@ -314,33 +325,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"command": "wdio ./wdio.conf.ts --logLevel=warn",
|
"command": "wdio ./wdio.conf.js --logLevel=warn",
|
||||||
"env": {
|
"env": {
|
||||||
"CI": "true",
|
"CI": "true",
|
||||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test:e2e": {
|
"test:e2e": {
|
||||||
"command": "wdio run ./tests/wdio.conf.ts",
|
"command": "wdio run ./tests/wdio.conf.js",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"build"
|
"build"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"CI": "true",
|
"CI": "true",
|
||||||
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
|
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test:e2e:watch": {
|
"test:e2e:watch": {
|
||||||
"command": "wdio run ./tests/wdio.conf.ts",
|
"command": "wdio run ./tests/wdio.conf.js",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"build"
|
"build"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
|
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test:watch": {
|
"test:watch": {
|
||||||
"command": "wdio run ./wdio.conf.ts",
|
"command": "wdio run ./wdio.conf.js",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"build"
|
"build"
|
||||||
],
|
],
|
||||||
|
|||||||
50
web/packages/core/id/index.js
Normal file
50
web/packages/core/id/index.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* @file Unique ID utilities.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A global ID generator.
|
||||||
|
*
|
||||||
|
* @singleton
|
||||||
|
* @runtime common
|
||||||
|
*
|
||||||
|
* @category IDs
|
||||||
|
*/
|
||||||
|
export class IDGenerator {
|
||||||
|
static #sequenceIndex = 0;
|
||||||
|
static #elementIndex = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ID for an HTML element.
|
||||||
|
*
|
||||||
|
* This ID will be unique for the lifetime of the page and will not be
|
||||||
|
* exposed on the `window` object.
|
||||||
|
*
|
||||||
|
* @param {string | number} [name] An optional name to use for the element.
|
||||||
|
*/
|
||||||
|
static elementID(name) {
|
||||||
|
name = name || ++this.#elementIndex;
|
||||||
|
|
||||||
|
return "«ak-" + name + "»";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ID.
|
||||||
|
*/
|
||||||
|
static next() {
|
||||||
|
this.#sequenceIndex += 1;
|
||||||
|
|
||||||
|
return this.#sequenceIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random ID in hexadecimal format.
|
||||||
|
*
|
||||||
|
* @param {number} [characterLength]
|
||||||
|
*/
|
||||||
|
static randomID(characterLength = 6) {
|
||||||
|
const bytes = crypto.getRandomValues(new Uint8Array(characterLength / 2));
|
||||||
|
|
||||||
|
return Array.from(bytes, (a) => a.toString(16)).join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
27
web/packages/core/promises/index.js
Normal file
27
web/packages/core/promises/index.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @file Helpers for running tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns a promise.
|
||||||
|
* @template {never[]} [A=never[]]
|
||||||
|
* @typedef {(...args: A) => Promise<unknown>} Thenable
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tuple of a function and its arguments.
|
||||||
|
* @template {Thenable} [T=Thenable]
|
||||||
|
* @typedef {[T, Parameters<T>]} SerializedThenable
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a sequence of promise-returning functions in series
|
||||||
|
* @template {Thenable[]} T
|
||||||
|
* @param {{ [K in keyof T]: [T[K], ...Parameters<T[K]>] }} sequence
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function series(...sequence) {
|
||||||
|
for (const [thenable, ...args] of sequence) {
|
||||||
|
await thenable(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
web/packages/core/types/node.d.ts
vendored
2
web/packages/core/types/node.d.ts
vendored
@ -14,7 +14,7 @@ declare module "module" {
|
|||||||
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
* const relativeDirname = dirname(fileURLToPath(import.meta.url));
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
var __dirname: string;
|
var __dirname: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
92
web/playwright.config.js
Normal file
92
web/playwright.config.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* @file Playwright configuration.
|
||||||
|
*
|
||||||
|
* @see https://playwright.dev/docs/test-configuration
|
||||||
|
*
|
||||||
|
* @import { LogFn, Logger } from "pino"
|
||||||
|
*/
|
||||||
|
import { ConsoleLogger } from "#logger/node";
|
||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
const CI = !!process.env.CI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Map<string, Logger>}
|
||||||
|
*/
|
||||||
|
const LoggerCache = new Map();
|
||||||
|
|
||||||
|
const baseURL = process.env.AK_TEST_RUNNER_PAGE_URL ?? "http://localhost:9000";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./test/browser",
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: CI,
|
||||||
|
retries: CI ? 2 : 0,
|
||||||
|
workers: CI ? 1 : undefined,
|
||||||
|
reporter: CI
|
||||||
|
? "github"
|
||||||
|
: [
|
||||||
|
// ---
|
||||||
|
["list", { printSteps: true }],
|
||||||
|
["html", { open: "never" }],
|
||||||
|
],
|
||||||
|
use: {
|
||||||
|
testIdAttribute: "data-test-id",
|
||||||
|
baseURL,
|
||||||
|
trace: "on-first-retry",
|
||||||
|
launchOptions: {
|
||||||
|
logger: {
|
||||||
|
isEnabled() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
log: (name, severity, message, args) => {
|
||||||
|
let logger = LoggerCache.get(name);
|
||||||
|
|
||||||
|
if (!logger) {
|
||||||
|
logger = ConsoleLogger.child({
|
||||||
|
name: `Playwright ${name.toUpperCase()}`,
|
||||||
|
});
|
||||||
|
LoggerCache.set(name, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {LogFn}
|
||||||
|
*/
|
||||||
|
let log;
|
||||||
|
|
||||||
|
switch (severity) {
|
||||||
|
case "verbose":
|
||||||
|
log = logger.debug;
|
||||||
|
break;
|
||||||
|
case "warning":
|
||||||
|
log = logger.warn;
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
log = logger.error;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log = logger.info;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === "api") {
|
||||||
|
log = logger.debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.call(logger, message.toString(), args);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
@ -6,8 +6,9 @@ import "@goauthentik/elements/EmptyState";
|
|||||||
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
|
import { ModalButton } from "@goauthentik/elements/buttons/ModalButton";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, css, html } from "lit";
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
|
import { createRef, ref } from "lit/directives/ref.js";
|
||||||
import { until } from "lit/directives/until.js";
|
import { until } from "lit/directives/until.js";
|
||||||
|
|
||||||
import PFAbout from "@patternfly/patternfly/components/AboutModalBox/about-modal-box.css";
|
import PFAbout from "@patternfly/patternfly/components/AboutModalBox/about-modal-box.css";
|
||||||
@ -16,16 +17,15 @@ import { AdminApi, CapabilitiesEnum, LicenseSummaryStatusEnum } from "@goauthent
|
|||||||
|
|
||||||
@customElement("ak-about-modal")
|
@customElement("ak-about-modal")
|
||||||
export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton)) {
|
export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton)) {
|
||||||
static get styles() {
|
static styles: CSSResult[] = [
|
||||||
return ModalButton.styles.concat(
|
...ModalButton.styles,
|
||||||
PFAbout,
|
PFAbout,
|
||||||
css`
|
css`
|
||||||
.pf-c-about-modal-box__hero {
|
.pf-c-about-modal-box__hero {
|
||||||
background-image: url("/static/dist/assets/images/flow_background.jpg");
|
background-image: url("/static/dist/assets/images/flow_background.jpg");
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
);
|
];
|
||||||
}
|
|
||||||
|
|
||||||
async getAboutEntries(): Promise<[string, string | TemplateResult][]> {
|
async getAboutEntries(): Promise<[string, string | TemplateResult][]> {
|
||||||
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||||
@ -55,21 +55,32 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderModal() {
|
#contentRef = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
#backdropListener = (event: PointerEvent) => {
|
||||||
|
// We only want to close the modal when the backdrop is clicked, not when it's children are clicked.
|
||||||
|
|
||||||
|
if (this.#contentRef.value?.contains(event.target as Node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override renderModal() {
|
||||||
let product = this.brandingTitle;
|
let product = this.brandingTitle;
|
||||||
|
|
||||||
if (this.licenseSummary.status !== LicenseSummaryStatusEnum.Unlicensed) {
|
if (this.licenseSummary.status !== LicenseSummaryStatusEnum.Unlicensed) {
|
||||||
product += ` ${msg("Enterprise")}`;
|
product += ` ${msg("Enterprise")}`;
|
||||||
}
|
}
|
||||||
return html`<div
|
return html`<div class="pf-c-backdrop" @click=${this.#backdropListener}>
|
||||||
class="pf-c-backdrop"
|
|
||||||
@click=${(e: PointerEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
this.closeModal();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="pf-l-bullseye">
|
<div class="pf-l-bullseye">
|
||||||
<div class="pf-c-about-modal-box" role="dialog" aria-modal="true">
|
<div
|
||||||
|
${ref(this.#contentRef)}
|
||||||
|
class="pf-c-about-modal-box"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="modal-title"
|
||||||
|
>
|
||||||
<div class="pf-c-about-modal-box__brand">
|
<div class="pf-c-about-modal-box__brand">
|
||||||
<img
|
<img
|
||||||
class="pf-c-about-modal-box__brand-image"
|
class="pf-c-about-modal-box__brand-image"
|
||||||
@ -78,18 +89,12 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-about-modal-box__close">
|
<div class="pf-c-about-modal-box__close">
|
||||||
<button
|
<button class="pf-c-button pf-m-plain" type="button" @click=${this.close}>
|
||||||
class="pf-c-button pf-m-plain"
|
|
||||||
type="button"
|
|
||||||
@click=${() => {
|
|
||||||
this.open = false;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i class="fas fa-times" aria-hidden="true"></i>
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-about-modal-box__header">
|
<div class="pf-c-about-modal-box__header">
|
||||||
<h1 class="pf-c-title pf-m-4xl">${product}</h1>
|
<h1 class="pf-c-title pf-m-4xl" id="modal-title">${product}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-about-modal-box__hero"></div>
|
<div class="pf-c-about-modal-box__hero"></div>
|
||||||
<div class="pf-c-about-modal-box__content">
|
<div class="pf-c-about-modal-box__content">
|
||||||
|
|||||||
@ -1,16 +1,44 @@
|
|||||||
|
import { SidebarItemProperties } from "#elements/sidebar/SidebarItem";
|
||||||
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 { 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 { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { repeat } from "lit/directives/repeat.js";
|
import { repeat } from "lit/directives/repeat.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a record-like object, prefixes each key with a dot, allowing it to be spread into a
|
||||||
|
* template literal.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* interface MyElementProperties {
|
||||||
|
* foo: string;
|
||||||
|
* bar: number;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const properties {} as LitPropertyRecord<MyElementProperties>
|
||||||
|
*
|
||||||
|
* console.log(properties) // { '.foo': string; '.bar': number }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export type LitPropertyRecord<T extends object> = {
|
||||||
|
[K in keyof T as K extends string ? LitPropertyKey<K> : never]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type that represents a property key that can be used in a LitPropertyRecord.
|
||||||
|
*
|
||||||
|
* @see {@linkcode LitPropertyRecord}
|
||||||
|
*/
|
||||||
|
export type LitPropertyKey<K> = K extends string ? `.${K}` | `?${K}` | K : K;
|
||||||
|
|
||||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
||||||
// commonplace and singular enough to merit its own handler.
|
// commonplace and singular enough to merit its own handler.
|
||||||
type SidebarEntry = [
|
type SidebarEntry = [
|
||||||
path: string | null,
|
path: string | null,
|
||||||
label: string,
|
label: string,
|
||||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
attributes?: LitPropertyRecord<SidebarItemProperties> | string[] | null,
|
||||||
children?: SidebarEntry[],
|
children?: SidebarEntry[],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -31,8 +59,7 @@ export function renderSidebarItem([
|
|||||||
properties.path = path;
|
properties.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<ak-sidebar-item ${spread(properties)}>
|
return html`<ak-sidebar-item label=${ifDefined(label)} ${spread(properties)}>
|
||||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
|
||||||
${children ? renderSidebarItems(children) : nothing}
|
${children ? renderSidebarItems(children) : nothing}
|
||||||
</ak-sidebar-item>`;
|
</ak-sidebar-item>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { me } from "#common/users";
|
|||||||
import { WebsocketClient } from "#common/ws";
|
import { WebsocketClient } from "#common/ws";
|
||||||
import { SidebarToggleEventDetail } from "#components/ak-page-header";
|
import { SidebarToggleEventDetail } from "#components/ak-page-header";
|
||||||
import { AuthenticatedInterface } from "#elements/AuthenticatedInterface";
|
import { AuthenticatedInterface } from "#elements/AuthenticatedInterface";
|
||||||
|
import "#elements/a11y/ak-skip-to-content";
|
||||||
import "#elements/ak-locale-context/ak-locale-context";
|
import "#elements/ak-locale-context/ak-locale-context";
|
||||||
import "#elements/banner/EnterpriseStatusBanner";
|
import "#elements/banner/EnterpriseStatusBanner";
|
||||||
import "#elements/banner/EnterpriseStatusBanner";
|
import "#elements/banner/EnterpriseStatusBanner";
|
||||||
@ -22,6 +23,7 @@ import "#elements/router/RouterOutlet";
|
|||||||
import "#elements/sidebar/Sidebar";
|
import "#elements/sidebar/Sidebar";
|
||||||
import "#elements/sidebar/SidebarItem";
|
import "#elements/sidebar/SidebarItem";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||||
import { customElement, eventOptions, property, query } from "lit/decorators.js";
|
import { customElement, eventOptions, property, query } from "lit/decorators.js";
|
||||||
import { classMap } from "lit/directives/class-map.js";
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
@ -162,16 +164,18 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
|||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated(): Promise<void> {
|
async firstUpdated(): Promise<void> {
|
||||||
this.user = await me();
|
me().then((session) => {
|
||||||
|
this.user = session;
|
||||||
|
|
||||||
const canAccessAdmin =
|
const canAccessAdmin =
|
||||||
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/");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
@ -190,13 +194,14 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
|||||||
};
|
};
|
||||||
|
|
||||||
return html` <ak-locale-context>
|
return html` <ak-locale-context>
|
||||||
|
<ak-skip-to-content></ak-skip-to-content>
|
||||||
<div class="pf-c-page">
|
<div class="pf-c-page">
|
||||||
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
|
<ak-page-navbar ?open=${this.sidebarOpen} @sidebar-toggle=${this.sidebarListener}>
|
||||||
<ak-version-banner></ak-version-banner>
|
<ak-version-banner></ak-version-banner>
|
||||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||||
</ak-page-navbar>
|
</ak-page-navbar>
|
||||||
|
|
||||||
<ak-sidebar class="${classMap(sidebarClasses)}">
|
<ak-sidebar ?hidden=${!this.sidebarOpen} class="${classMap(sidebarClasses)}">
|
||||||
${renderSidebarItems(AdminSidebarEntries)}
|
${renderSidebarItems(AdminSidebarEntries)}
|
||||||
${this.can(CapabilitiesEnum.IsEnterprise)
|
${this.can(CapabilitiesEnum.IsEnterprise)
|
||||||
? renderSidebarItems(AdminSidebarEnterpriseEntries)
|
? renderSidebarItems(AdminSidebarEnterpriseEntries)
|
||||||
@ -208,9 +213,10 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
|||||||
<div class="pf-c-drawer__main">
|
<div class="pf-c-drawer__main">
|
||||||
<div class="pf-c-drawer__content">
|
<div class="pf-c-drawer__content">
|
||||||
<div class="pf-c-drawer__body">
|
<div class="pf-c-drawer__body">
|
||||||
<main class="pf-c-page__main">
|
<div class="pf-c-page__main">
|
||||||
<ak-router-outlet
|
<ak-router-outlet
|
||||||
role="main"
|
role="main"
|
||||||
|
aria-label="${msg("Main content")}"
|
||||||
class="pf-c-page__main"
|
class="pf-c-page__main"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
id="main-content"
|
id="main-content"
|
||||||
@ -218,7 +224,7 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
|
|||||||
.routes=${ROUTES}
|
.routes=${ROUTES}
|
||||||
>
|
>
|
||||||
</ak-router-outlet>
|
</ak-router-outlet>
|
||||||
</main>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ak-notification-drawer
|
<ak-notification-drawer
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import { PFSize } from "@goauthentik/common/enums.js";
|
import { PFSize } from "@goauthentik/common/enums.js";
|
||||||
import {
|
import {
|
||||||
@ -13,7 +14,7 @@ import { state } from "lit/decorators.js";
|
|||||||
|
|
||||||
export interface AdminStatus {
|
export interface AdminStatus {
|
||||||
icon: string;
|
icon: string;
|
||||||
message?: TemplateResult;
|
message?: SlottedTemplateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,8 +99,8 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
|||||||
*
|
*
|
||||||
* @returns TemplateResult displaying the value
|
* @returns TemplateResult displaying the value
|
||||||
*/
|
*/
|
||||||
protected renderValue(): TemplateResult {
|
protected renderValue(): SlottedTemplateResult {
|
||||||
return html`${this.value}`;
|
return this.value ? html`${this.value}` : nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,7 +109,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
|||||||
* @param status - AdminStatus object containing icon and message
|
* @param status - AdminStatus object containing icon and message
|
||||||
* @returns TemplateResult for status display
|
* @returns TemplateResult for status display
|
||||||
*/
|
*/
|
||||||
private renderStatus(status: AdminStatus): TemplateResult {
|
private renderStatus(status: AdminStatus): SlottedTemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<p><i class="${status.icon}"></i> ${this.renderValue()}</p>
|
<p><i class="${status.icon}"></i> ${this.renderValue()}</p>
|
||||||
${status.message ? html`<p class="subtext">${status.message}</p>` : nothing}
|
${status.message ? html`<p class="subtext">${status.message}</p>` : nothing}
|
||||||
@ -121,9 +122,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
|||||||
* @param error - Error message to display
|
* @param error - Error message to display
|
||||||
* @returns TemplateResult for error display
|
* @returns TemplateResult for error display
|
||||||
*/
|
*/
|
||||||
private renderError(error: string): TemplateResult {
|
private renderError(error: string): SlottedTemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<p><i class="fa fa-times"></i> ${msg("Failed to fetch")}</p>
|
<p><i aria-hidden="true" class="fa fa-times"></i> ${msg("Failed to fetch")}</p>
|
||||||
<p class="subtext">${error}</p>
|
<p class="subtext">${error}</p>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -133,7 +134,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
|||||||
*
|
*
|
||||||
* @returns TemplateResult for loading spinner
|
* @returns TemplateResult for loading spinner
|
||||||
*/
|
*/
|
||||||
private renderLoading(): TemplateResult {
|
private renderLoading(): SlottedTemplateResult {
|
||||||
return html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`;
|
return html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +143,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
|||||||
*
|
*
|
||||||
* @returns TemplateResult for current component state
|
* @returns TemplateResult for current component state
|
||||||
*/
|
*/
|
||||||
renderInner(): TemplateResult {
|
renderInner(): SlottedTemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<p class="center-value">
|
<p class="center-value">
|
||||||
${
|
${
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
import {
|
import {
|
||||||
AdminStatus,
|
AdminStatus,
|
||||||
AdminStatusCard,
|
AdminStatusCard,
|
||||||
@ -5,7 +6,7 @@ import {
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html, nothing } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
|
|
||||||
import { AdminApi, OutpostsApi, SystemInfo } from "@goauthentik/api";
|
import { AdminApi, OutpostsApi, SystemInfo } from "@goauthentik/api";
|
||||||
@ -84,12 +85,12 @@ export class SystemStatusCard extends AdminStatusCard<SystemInfo> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeader(): TemplateResult {
|
renderHeader(): SlottedTemplateResult {
|
||||||
return html`${msg("System status")}`;
|
return msg("System status");
|
||||||
}
|
}
|
||||||
|
|
||||||
renderValue(): TemplateResult {
|
renderValue(): SlottedTemplateResult {
|
||||||
return html`${this.statusSummary}`;
|
return this.statusSummary ? html`${this.statusSummary}` : nothing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
import { AkControlElement } from "@goauthentik/elements/AkControlElement.js";
|
import {
|
||||||
|
AkControlElement,
|
||||||
|
formatFormElementAsJSON,
|
||||||
|
} from "@goauthentik/elements/AkControlElement.js";
|
||||||
import { type Spread } from "@goauthentik/elements/types";
|
import { type Spread } from "@goauthentik/elements/types";
|
||||||
import { spread } from "@open-wc/lit-helpers";
|
import { spread } from "@open-wc/lit-helpers";
|
||||||
|
|
||||||
@ -23,33 +26,32 @@ const hasLegalScheme = (url: string) =>
|
|||||||
|
|
||||||
@customElement("ak-admin-settings-footer-link")
|
@customElement("ak-admin-settings-footer-link")
|
||||||
export class FooterLinkInput extends AkControlElement<FooterLink> {
|
export class FooterLinkInput extends AkControlElement<FooterLink> {
|
||||||
static get styles() {
|
static styles = [
|
||||||
return [
|
PFBase,
|
||||||
PFBase,
|
PFInputGroup,
|
||||||
PFInputGroup,
|
PFFormControl,
|
||||||
PFFormControl,
|
css`
|
||||||
css`
|
.pf-c-input-group input#linkname {
|
||||||
.pf-c-input-group input#linkname {
|
flex-grow: 1;
|
||||||
flex-grow: 1;
|
width: 8rem;
|
||||||
width: 8rem;
|
}
|
||||||
}
|
`,
|
||||||
`,
|
];
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@property({ type: Object, attribute: false })
|
@property({ type: Object, attribute: false })
|
||||||
footerLink: FooterLink = {
|
public footerLink: FooterLink = {
|
||||||
name: "",
|
name: "",
|
||||||
href: "",
|
href: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
@queryAll(".ak-form-control")
|
@property({ type: String })
|
||||||
controls?: HTMLInputElement[];
|
public name?: string | null;
|
||||||
|
|
||||||
json() {
|
@queryAll(".ak-form-control")
|
||||||
return Object.fromEntries(
|
protected controls?: HTMLInputElement[];
|
||||||
Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
|
|
||||||
) as unknown as FooterLink;
|
public override json() {
|
||||||
|
return formatFormElementAsJSON<FooterLink>(this.controls);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isValid() {
|
get isValid() {
|
||||||
|
|||||||
@ -1,42 +1,49 @@
|
|||||||
import { render } from "@goauthentik/elements/tests/utils.js";
|
import { render } from "@goauthentik/elements/tests/utils.js";
|
||||||
import { $, expect } from "@wdio/globals";
|
import { $, browser, expect } from "@wdio/globals";
|
||||||
|
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
|
|
||||||
import "../AdminSettingsFooterLinks.js";
|
import "../AdminSettingsFooterLinks.js";
|
||||||
|
|
||||||
describe("ak-admin-settings-footer-link", () => {
|
describe("ak-admin-settings-footer-link", () => {
|
||||||
afterEach(async () => {
|
afterEach(() =>
|
||||||
await browser.execute(async () => {
|
browser.execute(() => {
|
||||||
await document.body.querySelector("ak-admin-settings-footer-link")?.remove();
|
document.body.querySelector("ak-admin-settings-footer-link")?.remove();
|
||||||
if (document.body._$litPart$) {
|
|
||||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
if ("_$litPart$" in document.body) {
|
||||||
await delete document.body._$litPart$;
|
delete document.body._$litPart$;
|
||||||
}
|
}
|
||||||
});
|
}),
|
||||||
});
|
);
|
||||||
|
|
||||||
it("should render an empty control", async () => {
|
it("should render an empty control", async () => {
|
||||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||||
const link = await $("ak-admin-settings-footer-link");
|
const link = $("ak-admin-settings-footer-link");
|
||||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
|
||||||
await expect(await link.getProperty("toJson")).toEqual({ name: "", href: "" });
|
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
|
||||||
|
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||||
|
name: "",
|
||||||
|
href: "",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be valid if just a name is filled in", async () => {
|
it("should not be valid if just a name is filled in", async () => {
|
||||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||||
const link = await $("ak-admin-settings-footer-link");
|
const link = $("ak-admin-settings-footer-link");
|
||||||
await link.$('input[name="name"]').setValue("foo");
|
await link.$('input[name="name"]').setValue("foo");
|
||||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
|
||||||
await expect(await link.getProperty("toJson")).toEqual({ name: "foo", href: "" });
|
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||||
|
name: "foo",
|
||||||
|
href: "",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be valid if just a URL is filled in", async () => {
|
it("should be valid if just a URL is filled in", async () => {
|
||||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||||
const link = await $("ak-admin-settings-footer-link");
|
const link = $("ak-admin-settings-footer-link");
|
||||||
await link.$('input[name="href"]').setValue("https://foo.com");
|
await link.$('input[name="href"]').setValue("https://foo.com");
|
||||||
await expect(await link.getProperty("isValid")).toStrictEqual(true);
|
await expect(link.getProperty("isValid")).resolves.toStrictEqual(true);
|
||||||
await expect(await link.getProperty("toJson")).toEqual({
|
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||||
name: "",
|
name: "",
|
||||||
href: "https://foo.com",
|
href: "https://foo.com",
|
||||||
});
|
});
|
||||||
@ -44,11 +51,13 @@ describe("ak-admin-settings-footer-link", () => {
|
|||||||
|
|
||||||
it("should be valid if both are filled in", async () => {
|
it("should be valid if both are filled in", async () => {
|
||||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||||
const link = await $("ak-admin-settings-footer-link");
|
const link = $("ak-admin-settings-footer-link");
|
||||||
|
|
||||||
await link.$('input[name="name"]').setValue("foo");
|
await link.$('input[name="name"]').setValue("foo");
|
||||||
await link.$('input[name="href"]').setValue("https://foo.com");
|
await link.$('input[name="href"]').setValue("https://foo.com");
|
||||||
await expect(await link.getProperty("isValid")).toStrictEqual(true);
|
|
||||||
await expect(await link.getProperty("toJson")).toEqual({
|
await expect(link.getProperty("isValid")).resolves.toStrictEqual(true);
|
||||||
|
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||||
name: "foo",
|
name: "foo",
|
||||||
href: "https://foo.com",
|
href: "https://foo.com",
|
||||||
});
|
});
|
||||||
@ -56,13 +65,13 @@ describe("ak-admin-settings-footer-link", () => {
|
|||||||
|
|
||||||
it("should not be valid if the URL is not valid", async () => {
|
it("should not be valid if the URL is not valid", async () => {
|
||||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||||
const link = await $("ak-admin-settings-footer-link");
|
const link = $("ak-admin-settings-footer-link");
|
||||||
await link.$('input[name="name"]').setValue("foo");
|
await link.$('input[name="name"]').setValue("foo");
|
||||||
await link.$('input[name="href"]').setValue("never://foo.com");
|
await link.$('input[name="href"]').setValue("never://foo.com");
|
||||||
await expect(await link.getProperty("toJson")).toEqual({
|
await expect(link.getProperty("toJson")).resolves.toEqual({
|
||||||
name: "foo",
|
name: "foo",
|
||||||
href: "never://foo.com",
|
href: "never://foo.com",
|
||||||
});
|
});
|
||||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
await expect(link.getProperty("isValid")).resolves.toStrictEqual(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -177,9 +177,8 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
|||||||
.options=${policyEngineModes}
|
.options=${policyEngineModes}
|
||||||
.value=${this.instance?.policyEngineMode}
|
.value=${this.instance?.policyEngineMode}
|
||||||
></ak-radio-input>
|
></ak-radio-input>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("UI settings")}">
|
||||||
<span slot="header"> ${msg("UI settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="metaLaunchUrl"
|
name="metaLaunchUrl"
|
||||||
label=${msg("Launch URL")}
|
label=${msg("Launch URL")}
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSidebarAfter(): TemplateResult {
|
protected renderSidebarAfter(): TemplateResult {
|
||||||
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
|
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
|
||||||
<div class="pf-c-card">
|
<div class="pf-c-card">
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import {
|
|||||||
WizardNavigationEvent,
|
WizardNavigationEvent,
|
||||||
WizardUpdateEvent,
|
WizardUpdateEvent,
|
||||||
} from "@goauthentik/components/ak-wizard/events";
|
} from "@goauthentik/components/ak-wizard/events";
|
||||||
import { KeyUnknown, serializeForm } from "@goauthentik/elements/forms/Form";
|
import type { AkControlElement } from "@goauthentik/elements/forms/Form";
|
||||||
|
import { serializeForm } from "@goauthentik/elements/forms/Form";
|
||||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
@ -29,22 +30,21 @@ export class ApplicationWizardStep extends WizardStep {
|
|||||||
|
|
||||||
// As recommended in [WizardStep](../../../components/ak-wizard/WizardStep.ts), we override
|
// As recommended in [WizardStep](../../../components/ak-wizard/WizardStep.ts), we override
|
||||||
// these fields and provide them to all the child classes.
|
// these fields and provide them to all the child classes.
|
||||||
wizardTitle = msg("New application");
|
protected wizardTitle = msg("New application");
|
||||||
wizardDescription = msg("Create a new application and configure a provider for it.");
|
protected wizardDescription = msg("Create a new application and configure a provider for it.");
|
||||||
canCancel = true;
|
public cancelable = true;
|
||||||
|
|
||||||
// This should be overridden in the children for more precise targeting.
|
// This should be overridden in the children for more precise targeting.
|
||||||
@query("form")
|
@query("form")
|
||||||
form!: HTMLFormElement;
|
protected form!: HTMLFormElement;
|
||||||
|
|
||||||
get formValues(): KeyUnknown | undefined {
|
get formValues(): Record<string, unknown> {
|
||||||
const elements = [
|
const elements = [
|
||||||
...Array.from(
|
...this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
|
||||||
this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
|
...this.form.querySelectorAll<AkControlElement>("[data-ak-control]"),
|
||||||
),
|
|
||||||
...Array.from(this.form.querySelectorAll<HTMLElement>("[data-ak-control=true]")),
|
|
||||||
];
|
];
|
||||||
return serializeForm(elements as unknown as NodeListOf<HorizontalFormElement>);
|
|
||||||
|
return serializeForm(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected removeErrors(
|
protected removeErrors(
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export class AkWizardTitle extends AKElement {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<div class="ak-bottom-spacing pf-c-content">
|
return html`<div class="ak-bottom-spacing pf-c-content">
|
||||||
<h3><slot></slot></h3>
|
<h3 data-test-id="wizard-heading"><slot></slot></h3>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,4 +33,12 @@ declare global {
|
|||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ak-wizard-title": AkWizardTitle;
|
"ak-wizard-title": AkWizardTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WizardTestIDMap {
|
||||||
|
heading: HTMLHeadingElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestIDSelectorMap {
|
||||||
|
wizard: WizardTestIDMap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { ApplicationWizardStep } from "@goauthentik/admin/applications/wizard/ApplicationWizardStep.js";
|
import { ApplicationWizardStep } from "@goauthentik/admin/applications/wizard/ApplicationWizardStep.js";
|
||||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title.js";
|
import "@goauthentik/admin/applications/wizard/ak-wizard-title.js";
|
||||||
import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes";
|
import { policyEngineModes } from "@goauthentik/admin/policies/PolicyEngineModes";
|
||||||
import { camelToSnake } from "@goauthentik/common/utils.js";
|
|
||||||
import "@goauthentik/components/ak-radio-input";
|
import "@goauthentik/components/ak-radio-input";
|
||||||
import "@goauthentik/components/ak-slug-input";
|
import "@goauthentik/components/ak-slug-input";
|
||||||
import "@goauthentik/components/ak-switch-input";
|
import "@goauthentik/components/ak-switch-input";
|
||||||
import "@goauthentik/components/ak-text-input";
|
import "@goauthentik/components/ak-text-input";
|
||||||
import { type NavigableButton, type WizardButton } from "@goauthentik/components/ak-wizard/types";
|
import { type NavigableButton, type WizardButton } from "@goauthentik/components/ak-wizard/types";
|
||||||
import { type KeyUnknown } from "@goauthentik/elements/forms/Form";
|
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { isSlug } from "@goauthentik/elements/router/utils.js";
|
import { isSlug } from "@goauthentik/elements/router/utils.js";
|
||||||
|
import { snakeCase } from "change-case";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
@ -23,11 +22,10 @@ import { ApplicationWizardStateUpdate, ValidationRecord } from "../types";
|
|||||||
|
|
||||||
const autoTrim = (v: unknown) => (typeof v === "string" ? v.trim() : v);
|
const autoTrim = (v: unknown) => (typeof v === "string" ? v.trim() : v);
|
||||||
|
|
||||||
const trimMany = (o: KeyUnknown, vs: string[]) =>
|
const trimMany = (o: Record<string, unknown>, vs: string[]) =>
|
||||||
Object.fromEntries(vs.map((v) => [v, autoTrim(o[v])]));
|
Object.fromEntries(vs.map((v) => [v, autoTrim(o[v])]));
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const isStr = (v: unknown): v is string => typeof v === "string";
|
||||||
const isStr = (v: any): v is string => typeof v === "string";
|
|
||||||
|
|
||||||
@customElement("ak-application-wizard-application-step")
|
@customElement("ak-application-wizard-application-step")
|
||||||
export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||||
@ -48,9 +46,7 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
|||||||
errorMessages(name: string) {
|
errorMessages(name: string) {
|
||||||
return this.errors.has(name)
|
return this.errors.has(name)
|
||||||
? [this.errors.get(name)]
|
? [this.errors.get(name)]
|
||||||
: (this.wizard.errors?.app?.[name] ??
|
: (this.wizard.errors?.app?.[name] ?? this.wizard.errors?.app?.[snakeCase(name)] ?? []);
|
||||||
this.wizard.errors?.app?.[camelToSnake(name)] ??
|
|
||||||
[]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get buttons(): WizardButton[] {
|
get buttons(): WizardButton[] {
|
||||||
@ -146,9 +142,8 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
|||||||
.value=${app.policyEngineMode}
|
.value=${app.policyEngineMode}
|
||||||
.errorMessages=${errors.policyEngineMode ?? []}
|
.errorMessages=${errors.policyEngineMode ?? []}
|
||||||
></ak-radio-input>
|
></ak-radio-input>
|
||||||
<ak-form-group aria-label=${msg("UI Settings")}>
|
<ak-form-group label=${msg("UI Settings")}>
|
||||||
<span slot="header"> ${msg("UI Settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="metaLaunchUrl"
|
name="metaLaunchUrl"
|
||||||
label=${msg("Launch URL")}
|
label=${msg("Launch URL")}
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { camelToSnake } from "@goauthentik/common/utils.js";
|
|
||||||
import "@goauthentik/components/ak-number-input";
|
import "@goauthentik/components/ak-number-input";
|
||||||
import "@goauthentik/components/ak-radio-input";
|
import "@goauthentik/components/ak-radio-input";
|
||||||
import "@goauthentik/components/ak-switch-input";
|
import "@goauthentik/components/ak-switch-input";
|
||||||
import "@goauthentik/components/ak-text-input";
|
import "@goauthentik/components/ak-text-input";
|
||||||
import { AKElement } from "@goauthentik/elements/Base.js";
|
import { AKElement } from "@goauthentik/elements/Base.js";
|
||||||
import { KeyUnknown, serializeForm } from "@goauthentik/elements/forms/Form";
|
import type { AkControlElement } from "@goauthentik/elements/forms/Form";
|
||||||
|
import { serializeForm } from "@goauthentik/elements/forms/Form";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
import { snakeCase } from "change-case";
|
||||||
|
|
||||||
import { property, query } from "lit/decorators.js";
|
import { property, query } from "lit/decorators.js";
|
||||||
|
|
||||||
@ -30,14 +31,13 @@ export class ApplicationWizardProviderForm<T extends OneOfProvider> extends AKEl
|
|||||||
@query("form#providerform")
|
@query("form#providerform")
|
||||||
form!: HTMLFormElement;
|
form!: HTMLFormElement;
|
||||||
|
|
||||||
get formValues(): KeyUnknown | undefined {
|
get formValues(): Record<string, unknown> {
|
||||||
const elements = [
|
const elements = [
|
||||||
...Array.from(
|
...this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
|
||||||
this.form.querySelectorAll<HorizontalFormElement>("ak-form-element-horizontal"),
|
...this.form.querySelectorAll<AkControlElement>("[data-ak-control]"),
|
||||||
),
|
|
||||||
...Array.from(this.form.querySelectorAll<HTMLElement>("[data-ak-control=true]")),
|
|
||||||
];
|
];
|
||||||
return serializeForm(elements as unknown as NodeListOf<HorizontalFormElement>);
|
|
||||||
|
return serializeForm(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
get valid() {
|
get valid() {
|
||||||
@ -49,7 +49,7 @@ export class ApplicationWizardProviderForm<T extends OneOfProvider> extends AKEl
|
|||||||
return name in this.errors
|
return name in this.errors
|
||||||
? [this.errors[name]]
|
? [this.errors[name]]
|
||||||
: (this.wizard.errors?.provider?.[name] ??
|
: (this.wizard.errors?.provider?.[name] ??
|
||||||
this.wizard.errors?.provider?.[camelToSnake(name)] ??
|
this.wizard.errors?.provider?.[snakeCase(name)] ??
|
||||||
[]);
|
[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -60,9 +60,8 @@ export class ApplicationWizardRACProviderForm extends ApplicationWizardProviderF
|
|||||||
input-hint="code"
|
input-hint="code"
|
||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label=" ${msg("Protocol settings")} ">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Property mappings")}
|
label=${msg("Property mappings")}
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
|
|||||||
@ -176,9 +176,8 @@ export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Additional settings")}">
|
||||||
<span slot="header">${msg("Additional settings")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Context")} name="context">
|
<ak-form-element-horizontal label=${msg("Context")} name="context">
|
||||||
<ak-codemirror
|
<ak-codemirror
|
||||||
mode=${CodeMirrorMode.YAML}
|
mode=${CodeMirrorMode.YAML}
|
||||||
|
|||||||
@ -87,9 +87,8 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Branding settings")} ">
|
||||||
<span slot="header"> ${msg("Branding settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Title")} required name="brandingTitle">
|
<ak-form-element-horizontal label=${msg("Title")} required name="brandingTitle">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -170,9 +169,8 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("External user settings")} ">
|
||||||
<span slot="header"> ${msg("External user settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Default application")}
|
label=${msg("Default application")}
|
||||||
name="defaultApplication"
|
name="defaultApplication"
|
||||||
@ -215,9 +213,8 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Default flows")} ">
|
||||||
<span slot="header"> ${msg("Default flows")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Authentication flow")}
|
label=${msg("Authentication flow")}
|
||||||
name="flowAuthentication"
|
name="flowAuthentication"
|
||||||
@ -295,9 +292,8 @@ export class BrandForm extends ModelForm<Brand, string> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Other global settings")} ">
|
||||||
<span slot="header"> ${msg("Other global settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Web Certificate")}
|
label=${msg("Web Certificate")}
|
||||||
name="webCertificate"
|
name="webCertificate"
|
||||||
|
|||||||
@ -44,19 +44,18 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) {
|
|||||||
* @attr
|
* @attr
|
||||||
*/
|
*/
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
group?: string;
|
public group?: string;
|
||||||
|
|
||||||
@query("ak-search-select")
|
@query("ak-search-select")
|
||||||
search!: SearchSelect<Group>;
|
public search!: SearchSelect<Group>;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
name: string | null | undefined;
|
public name?: string | null;
|
||||||
|
|
||||||
selectedGroup?: Group;
|
selectedGroup?: Group;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.selected = this.selected.bind(this);
|
|
||||||
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
|
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +82,9 @@ export class CoreGroupSearch extends CustomListenerElement(AKElement) {
|
|||||||
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
selected(group: Group) {
|
selected = (group: Group) => {
|
||||||
return this.group === group.pk;
|
return this.group === group.pk;
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
|
|||||||
@ -32,13 +32,19 @@ const renderValue = (item: CertificateKeyPair | undefined): string | undefined =
|
|||||||
@customElement("ak-crypto-certificate-search")
|
@customElement("ak-crypto-certificate-search")
|
||||||
export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement) {
|
export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement) {
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
certificate?: string;
|
public certificate?: string;
|
||||||
|
|
||||||
@query("ak-search-select")
|
@query("ak-search-select")
|
||||||
search!: SearchSelect<CertificateKeyPair>;
|
public search!: SearchSelect<CertificateKeyPair>;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
name: string | null | undefined;
|
public name?: string | null;
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
public label?: string | undefined;
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
public placeholder?: string | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to `true` to allow certificates without private key to show up. When set to `false`,
|
* Set to `true` to allow certificates without private key to show up. When set to `false`,
|
||||||
@ -46,7 +52,7 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
|||||||
* @attr
|
* @attr
|
||||||
*/
|
*/
|
||||||
@property({ type: Boolean, attribute: "nokey" })
|
@property({ type: Boolean, attribute: "nokey" })
|
||||||
noKey = false;
|
public noKey = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set this to true if, should there be only one certificate available, you want the system to
|
* Set this to true if, should there be only one certificate available, you want the system to
|
||||||
@ -55,16 +61,12 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
|||||||
* @attr
|
* @attr
|
||||||
*/
|
*/
|
||||||
@property({ type: Boolean, attribute: "singleton" })
|
@property({ type: Boolean, attribute: "singleton" })
|
||||||
singleton = false;
|
public singleton = false;
|
||||||
|
|
||||||
selectedKeypair?: CertificateKeyPair;
|
/**
|
||||||
|
* @todo Document this.
|
||||||
constructor() {
|
*/
|
||||||
super();
|
public selectedKeypair?: CertificateKeyPair;
|
||||||
this.selected = this.selected.bind(this);
|
|
||||||
this.fetchObjects = this.fetchObjects.bind(this);
|
|
||||||
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this.selectedKeypair ? renderValue(this.selectedKeypair) : null;
|
return this.selectedKeypair ? renderValue(this.selectedKeypair) : null;
|
||||||
@ -83,13 +85,13 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchUpdate(ev: CustomEvent) {
|
handleSearchUpdate = (ev: CustomEvent) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.selectedKeypair = ev.detail.value;
|
this.selectedKeypair = ev.detail.value;
|
||||||
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
||||||
}
|
};
|
||||||
|
|
||||||
async fetchObjects(query?: string): Promise<CertificateKeyPair[]> {
|
fetchObjects = async (query?: string): Promise<CertificateKeyPair[]> => {
|
||||||
const args: CryptoCertificatekeypairsListRequest = {
|
const args: CryptoCertificatekeypairsListRequest = {
|
||||||
ordering: "name",
|
ordering: "name",
|
||||||
hasKey: !this.noKey,
|
hasKey: !this.noKey,
|
||||||
@ -102,19 +104,21 @@ export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement)
|
|||||||
args,
|
args,
|
||||||
);
|
);
|
||||||
return certificates.results;
|
return certificates.results;
|
||||||
}
|
};
|
||||||
|
|
||||||
selected(item: CertificateKeyPair, items: CertificateKeyPair[]) {
|
selected = (item: CertificateKeyPair, items: CertificateKeyPair[]) => {
|
||||||
return (
|
return (
|
||||||
(this.singleton && !this.certificate && items.length === 1) ||
|
(this.singleton && !this.certificate && items.length === 1) ||
|
||||||
(!!this.certificate && this.certificate === item.pk)
|
(!!this.certificate && this.certificate === item.pk)
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
name=${ifDefined(this.name ?? undefined)}
|
name=${ifDefined(this.name ?? undefined)}
|
||||||
|
label=${ifDefined(this.label ?? undefined)}
|
||||||
|
placeholder=${ifDefined(this.placeholder ?? undefined)}
|
||||||
.fetchObjects=${this.fetchObjects}
|
.fetchObjects=${this.fetchObjects}
|
||||||
.renderElement=${renderElement}
|
.renderElement=${renderElement}
|
||||||
.value=${renderValue}
|
.value=${renderValue}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
|
|||||||
import "@goauthentik/elements/forms/SearchSelect";
|
import "@goauthentik/elements/forms/SearchSelect";
|
||||||
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
|
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
import { property, query } from "lit/decorators.js";
|
import { property, query } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
@ -34,13 +35,15 @@ export function getFlowValue(flow: Flow | undefined): string | undefined {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement) {
|
export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement) {
|
||||||
|
//#region Properties
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of flow we're looking for.
|
* The type of flow we're looking for.
|
||||||
*
|
*
|
||||||
* @attr
|
* @attr
|
||||||
*/
|
*/
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
flowType?: FlowsInstancesListDesignationEnum;
|
public flowType?: FlowsInstancesListDesignationEnum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the current flow, if any. For stages where the flow is already defined.
|
* The id of the current flow, if any. For stages where the flow is already defined.
|
||||||
@ -48,7 +51,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
|||||||
* @attr
|
* @attr
|
||||||
*/
|
*/
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
currentFlow?: string | undefined;
|
public currentFlow?: string | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, it is not valid to leave the flow blank.
|
* If true, it is not valid to leave the flow blank.
|
||||||
@ -56,10 +59,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
|||||||
* @attr
|
* @attr
|
||||||
*/
|
*/
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
required?: boolean = false;
|
public required?: boolean = false;
|
||||||
|
|
||||||
@query("ak-search-select")
|
|
||||||
search!: SearchSelect<T>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When specified and the object instance does not have a flow selected, auto-select the flow with the given slug.
|
* When specified and the object instance does not have a flow selected, auto-select the flow with the given slug.
|
||||||
@ -70,9 +70,29 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
|||||||
defaultFlowSlug?: string;
|
defaultFlowSlug?: string;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
name: string | null | undefined;
|
public name?: string | null;
|
||||||
|
|
||||||
selectedFlow?: T;
|
/**
|
||||||
|
* The label of the input, for forms.
|
||||||
|
*
|
||||||
|
* @attr
|
||||||
|
*/
|
||||||
|
@property({ type: String })
|
||||||
|
public label?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The textual placeholder for the search's <input> object, if currently empty. Used as the
|
||||||
|
* native <input> object's `placeholder` field.
|
||||||
|
*
|
||||||
|
* @attr
|
||||||
|
*/
|
||||||
|
@property({ type: String })
|
||||||
|
public placeholder: string = msg("Select a flow...");
|
||||||
|
|
||||||
|
@query("ak-search-select")
|
||||||
|
protected search!: SearchSelect<T>;
|
||||||
|
|
||||||
|
protected selectedFlow?: T;
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this.selectedFlow ? getFlowValue(this.selectedFlow) : null;
|
return this.selectedFlow ? getFlowValue(this.selectedFlow) : null;
|
||||||
@ -80,18 +100,16 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.fetchObjects = this.fetchObjects.bind(this);
|
|
||||||
this.selected = this.selected.bind(this);
|
this.selected = this.selected.bind(this);
|
||||||
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchUpdate(ev: CustomEvent) {
|
handleSearchUpdate = (ev: CustomEvent) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.selectedFlow = ev.detail.value;
|
this.selectedFlow = ev.detail.value;
|
||||||
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
||||||
}
|
};
|
||||||
|
|
||||||
async fetchObjects(query?: string): Promise<Flow[]> {
|
fetchObjects = async (query?: string): Promise<Flow[]> => {
|
||||||
const args: FlowsInstancesListRequest = {
|
const args: FlowsInstancesListRequest = {
|
||||||
ordering: "slug",
|
ordering: "slug",
|
||||||
designation: this.flowType,
|
designation: this.flowType,
|
||||||
@ -99,7 +117,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
|||||||
};
|
};
|
||||||
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
|
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}
|
};
|
||||||
|
|
||||||
/* This is the most commonly overridden method of this class. About half of the Flow Searches
|
/* This is the most commonly overridden method of this class. About half of the Flow Searches
|
||||||
* use this method, but several have more complex needs, such as relating to the brand, or just
|
* use this method, but several have more complex needs, such as relating to the brand, or just
|
||||||
@ -134,6 +152,8 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
|||||||
.renderElement=${renderElement}
|
.renderElement=${renderElement}
|
||||||
.renderDescription=${renderDescription}
|
.renderDescription=${renderDescription}
|
||||||
.value=${getFlowValue}
|
.value=${getFlowValue}
|
||||||
|
placeholder=${ifDefined(this.placeholder ?? undefined)}
|
||||||
|
label=${ifDefined(this.label ?? undefined)}
|
||||||
name=${ifDefined(this.name ?? undefined)}
|
name=${ifDefined(this.name ?? undefined)}
|
||||||
@ak-change=${this.handleSearchUpdate}
|
@ak-change=${this.handleSearchUpdate}
|
||||||
?blankable=${!this.required}
|
?blankable=${!this.required}
|
||||||
|
|||||||
@ -21,14 +21,9 @@ export class AkBrandedFlowSearch<T extends Flow> extends FlowSearch<T> {
|
|||||||
@property({ attribute: false, type: String })
|
@property({ attribute: false, type: String })
|
||||||
brandFlow?: string;
|
brandFlow?: string;
|
||||||
|
|
||||||
constructor() {
|
public selected = (flow: Flow): boolean => {
|
||||||
super();
|
|
||||||
this.selected = this.selected.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
selected(flow: Flow): boolean {
|
|
||||||
return super.selected(flow) || flow.pk === this.brandFlow;
|
return super.selected(flow) || flow.pk === this.brandFlow;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@ -32,19 +32,14 @@ export class AkSourceFlowSearch<T extends Flow> extends FlowSearch<T> {
|
|||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
instanceId: string | undefined;
|
instanceId: string | undefined;
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.selected = this.selected.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's no instance or no currentFlowId for it and the flow resembles the fallback,
|
// If there's no instance or no currentFlowId for it and the flow resembles the fallback,
|
||||||
// otherwise defer to the parent class.
|
// otherwise defer to the parent class.
|
||||||
selected(flow: Flow): boolean {
|
selected = (flow: Flow): boolean => {
|
||||||
return (
|
return (
|
||||||
(!this.instanceId && !this.currentFlow && flow.slug === this.fallback) ||
|
(!this.instanceId && !this.currentFlow && flow.slug === this.fallback) ||
|
||||||
super.selected(flow)
|
super.selected(flow)
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@ -8,25 +8,35 @@ import { html, nothing } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
@customElement("ak-license-notice")
|
@customElement("ak-license-notice")
|
||||||
export class AkLicenceNotice extends WithLicenseSummary(AKElement) {
|
export class AKLicenceNotice extends WithLicenseSummary(AKElement) {
|
||||||
static styles = [$PFBase];
|
static styles = [$PFBase];
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
notice = msg("Enterprise only");
|
public label = msg("Enterprise only");
|
||||||
|
|
||||||
|
@property()
|
||||||
|
public description = msg("Learn more about the enterprise license.");
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.hasEnterpriseLicense
|
if (this.hasEnterpriseLicense) {
|
||||||
? nothing
|
return nothing;
|
||||||
: html`
|
}
|
||||||
<ak-alert class="pf-c-radio__description" inline plain>
|
|
||||||
<a href="#/enterprise/licenses">${this.notice}</a>
|
return html`
|
||||||
</ak-alert>
|
<ak-alert class="pf-c-radio__description" inline plain>
|
||||||
`;
|
<a
|
||||||
|
aria-label="${this.label}"
|
||||||
|
aria-description="${this.description}"
|
||||||
|
href="#/enterprise/licenses"
|
||||||
|
>${this.label}</a
|
||||||
|
>
|
||||||
|
</ak-alert>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ak-license-notice": AkLicenceNotice;
|
"ak-license-notice": AKLicenceNotice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ describe("ak-enterprise-status-card", () => {
|
|||||||
it("should not error when no data is loaded", async () => {
|
it("should not error when no data is loaded", async () => {
|
||||||
render(html`<ak-enterprise-status-card></ak-enterprise-status-card>`);
|
render(html`<ak-enterprise-status-card></ak-enterprise-status-card>`);
|
||||||
|
|
||||||
const status = await $("ak-enterprise-status-card");
|
const status = $("ak-enterprise-status-card");
|
||||||
await expect(status).toHaveText(msg("Loading"));
|
await expect(status).resolves.toHaveText(msg("Loading"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render empty when unlicensed", async () => {
|
it("should render empty when unlicensed", async () => {
|
||||||
@ -35,22 +35,22 @@ describe("ak-enterprise-status-card", () => {
|
|||||||
</ak-enterprise-status-card>`,
|
</ak-enterprise-status-card>`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const status = await $("ak-enterprise-status-card").$(
|
const status = $("ak-enterprise-status-card").$(
|
||||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||||
);
|
);
|
||||||
await expect(status).toExist();
|
await expect(status).resolves.toExist();
|
||||||
await expect(status).toHaveText(msg("Unlicensed"));
|
await expect(status).resolves.toHaveText(msg("Unlicensed"));
|
||||||
|
|
||||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
const internalUserProgress = $("ak-enterprise-status-card").$(
|
||||||
">>>#internalUsers > .pf-c-progress__bar",
|
">>>#internalUsers > .pf-c-progress__bar",
|
||||||
);
|
);
|
||||||
await expect(internalUserProgress).toExist();
|
await expect(internalUserProgress).resolves.toExist();
|
||||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "0");
|
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "0");
|
||||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
const externalUserProgress = $("ak-enterprise-status-card").$(
|
||||||
">>>#externalUsers > .pf-c-progress__bar",
|
">>>#externalUsers > .pf-c-progress__bar",
|
||||||
);
|
);
|
||||||
await expect(externalUserProgress).toExist();
|
await expect(externalUserProgress).resolves.toExist();
|
||||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "0");
|
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "0");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show warnings when full", async () => {
|
it("should show warnings when full", async () => {
|
||||||
@ -72,34 +72,35 @@ describe("ak-enterprise-status-card", () => {
|
|||||||
</ak-enterprise-status-card>`,
|
</ak-enterprise-status-card>`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const status = await $("ak-enterprise-status-card").$(
|
const status = $("ak-enterprise-status-card").$(
|
||||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||||
);
|
);
|
||||||
await expect(status).toExist();
|
await expect(status).resolves.toExist();
|
||||||
await expect(status).toHaveText(msg("Valid"));
|
await expect(status).resolves.toHaveText(msg("Valid"));
|
||||||
|
|
||||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
const internalUserProgress = $("ak-enterprise-status-card").$(
|
||||||
">>>#internalUsers > .pf-c-progress__bar",
|
">>>#internalUsers > .pf-c-progress__bar",
|
||||||
);
|
);
|
||||||
await expect(internalUserProgress).toExist();
|
await expect(internalUserProgress).resolves.toExist();
|
||||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
|
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
|
||||||
|
|
||||||
await expect(
|
await expect($("ak-enterprise-status-card").$(">>>#internalUsers")).toHaveElementClass(
|
||||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
"pf-m-warning",
|
||||||
).toHaveElementClass("pf-m-warning");
|
);
|
||||||
|
|
||||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
const externalUserProgress = $("ak-enterprise-status-card").$(
|
||||||
">>>#externalUsers > .pf-c-progress__bar",
|
">>>#externalUsers > .pf-c-progress__bar",
|
||||||
);
|
);
|
||||||
await expect(externalUserProgress).toExist();
|
await expect(externalUserProgress).resolves.toExist();
|
||||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "100");
|
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
$("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||||
).toHaveElementClass("pf-m-warning");
|
).resolves.toHaveElementClass("pf-m-warning");
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
|
$("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||||
).toHaveElementClass("pf-m-warning");
|
).resolves.toHaveElementClass("pf-m-warning");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show infinity when not licensed for a user type", async () => {
|
it("should show infinity when not licensed for a user type", async () => {
|
||||||
@ -121,33 +122,33 @@ describe("ak-enterprise-status-card", () => {
|
|||||||
</ak-enterprise-status-card>`,
|
</ak-enterprise-status-card>`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const status = await $("ak-enterprise-status-card").$(
|
const status = $("ak-enterprise-status-card").$(
|
||||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||||
);
|
);
|
||||||
await expect(status).toExist();
|
await expect(status).resolves.toExist();
|
||||||
await expect(status).toHaveText(msg("Valid"));
|
await expect(status).resolves.toHaveText(msg("Valid"));
|
||||||
|
|
||||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
const internalUserProgress = $("ak-enterprise-status-card").$(
|
||||||
">>>#internalUsers > .pf-c-progress__bar",
|
">>>#internalUsers > .pf-c-progress__bar",
|
||||||
);
|
);
|
||||||
await expect(internalUserProgress).toExist();
|
await expect(internalUserProgress).resolves.toExist();
|
||||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
|
await expect(internalUserProgress).resolves.toHaveAttr("aria-valuenow", "100");
|
||||||
|
|
||||||
await expect(
|
await expect($("ak-enterprise-status-card").$(">>>#internalUsers")).toHaveElementClass(
|
||||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
"pf-m-warning",
|
||||||
).toHaveElementClass("pf-m-warning");
|
);
|
||||||
|
|
||||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
const externalUserProgress = $("ak-enterprise-status-card").$(
|
||||||
">>>#externalUsers > .pf-c-progress__bar",
|
">>>#externalUsers > .pf-c-progress__bar",
|
||||||
);
|
);
|
||||||
await expect(externalUserProgress).toExist();
|
await expect(externalUserProgress).resolves.toExist();
|
||||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "∞");
|
await expect(externalUserProgress).resolves.toHaveAttr("aria-valuenow", "∞");
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
$("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||||
).toHaveElementClass("pf-m-warning");
|
).resolves.toHaveElementClass("pf-m-warning");
|
||||||
await expect(
|
await expect(
|
||||||
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
|
$("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||||
).toHaveElementClass("pf-m-danger");
|
).resolves.toHaveElementClass("pf-m-danger");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -211,9 +211,8 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
|||||||
${msg("Required authentication level for this flow.")}
|
${msg("Required authentication level for this flow.")}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Behavior settings")}">
|
||||||
<span slot="header"> ${msg("Behavior settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="compatibilityMode">
|
<ak-form-element-horizontal name="compatibilityMode">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
@ -286,9 +285,8 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Appearance settings")}">
|
||||||
<span slot="header"> ${msg("Appearance settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Layout")} required name="layout">
|
<ak-form-element-horizontal label=${msg("Layout")} required name="layout">
|
||||||
<select class="pf-c-form-control">
|
<select class="pf-c-form-control">
|
||||||
<option
|
<option
|
||||||
|
|||||||
@ -231,9 +231,8 @@ export class OutpostForm extends ModelForm<Outpost, string> {
|
|||||||
selected-label="${msg("Selected Applications")}"
|
selected-label="${msg("Selected Applications")}"
|
||||||
></ak-dual-select-provider>
|
></ak-dual-select-provider>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group aria-label=${msg("Advanced settings")}>
|
<ak-form-group label=${msg("Advanced settings")}>
|
||||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Configuration")} name="config">
|
<ak-form-element-horizontal label=${msg("Configuration")} name="config">
|
||||||
<ak-codemirror
|
<ak-codemirror
|
||||||
mode=${CodeMirrorMode.YAML}
|
mode=${CodeMirrorMode.YAML}
|
||||||
|
|||||||
@ -64,9 +64,8 @@ export class DummyPolicyForm extends BasePolicyForm<DummyPolicy> {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="result">
|
<ak-form-element-horizontal name="result">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -76,9 +76,8 @@ export class EventMatcherPolicyForm extends BasePolicyForm<EventMatcherPolicy> {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Action")} name="action">
|
<ak-form-element-horizontal label=${msg("Action")} name="action">
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
.fetchObjects=${async (query?: string): Promise<TypeCreate[]> => {
|
.fetchObjects=${async (query?: string): Promise<TypeCreate[]> => {
|
||||||
|
|||||||
@ -64,9 +64,8 @@ export class PasswordExpiryPolicyForm extends BasePolicyForm<PasswordExpiryPolic
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Maximum age (in days)")}
|
label=${msg("Maximum age (in days)")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -67,9 +67,8 @@ export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Expression")}
|
label=${msg("Expression")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -78,9 +78,8 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Distance settings")}">
|
||||||
<span slot="header"> ${msg("Distance settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="checkHistoryDistance">
|
<ak-form-element-horizontal name="checkHistoryDistance">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
@ -185,9 +184,8 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Static rule settings")}">
|
||||||
<span slot="header">${msg("Static rule settings")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("ASNs")} name="asns">
|
<ak-form-element-horizontal label=${msg("ASNs")} name="asns">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@ -44,9 +44,8 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderStaticRules(): TemplateResult {
|
renderStaticRules(): TemplateResult {
|
||||||
return html` <ak-form-group>
|
return html` <ak-form-group label="${msg("Static rules")}">
|
||||||
<span slot="header"> ${msg("Static rules")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Minimum length")}
|
label=${msg("Minimum length")}
|
||||||
required
|
required
|
||||||
@ -142,9 +141,8 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
|||||||
|
|
||||||
renderHIBP(): TemplateResult {
|
renderHIBP(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("HaveIBeenPwned settings")}">
|
||||||
<span slot="header"> ${msg("HaveIBeenPwned settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Allowed count")}
|
label=${msg("Allowed count")}
|
||||||
required
|
required
|
||||||
@ -167,9 +165,8 @@ export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
|||||||
|
|
||||||
renderZxcvbn(): TemplateResult {
|
renderZxcvbn(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("zxcvbn settings")}">
|
||||||
<span slot="header"> ${msg("zxcvbn settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Score threshold")}
|
label=${msg("Score threshold")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -74,9 +74,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Policy-specific settings")}">
|
||||||
<span slot="header"> ${msg("Policy-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="checkIp">
|
<ak-form-element-horizontal name="checkIp">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -62,9 +62,8 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("General settings")}">
|
||||||
<span slot="header"> ${msg("General settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Username")}
|
label=${msg("Username")}
|
||||||
name="staticSettings.username"
|
name="staticSettings.username"
|
||||||
@ -89,9 +88,8 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("RDP settings")}">
|
||||||
<span slot="header"> ${msg("RDP settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Ignore server certificate")}
|
label=${msg("Ignore server certificate")}
|
||||||
name="staticSettings.ignore-cert"
|
name="staticSettings.ignore-cert"
|
||||||
@ -134,9 +132,8 @@ export class PropertyMappingProviderRACForm extends BasePropertyMappingForm<RACP
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced settings")}">
|
||||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Expression")}
|
label=${msg("Expression")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
|||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
|
|
||||||
export abstract class BaseProviderForm<T> extends ModelForm<T, number> {
|
export abstract class BaseProviderForm<T> extends ModelForm<T, number> {
|
||||||
getSuccessMessage(): string {
|
public override getSuccessMessage(): string {
|
||||||
return this.instance
|
return this.instance
|
||||||
? msg("Successfully updated provider.")
|
? msg("Successfully updated provider.")
|
||||||
: msg("Successfully created provider.");
|
: msg("Successfully created provider.");
|
||||||
|
|||||||
@ -28,32 +28,38 @@ import { Provider, ProvidersApi } from "@goauthentik/api";
|
|||||||
|
|
||||||
@customElement("ak-provider-list")
|
@customElement("ak-provider-list")
|
||||||
export class ProviderListPage extends TablePage<Provider> {
|
export class ProviderListPage extends TablePage<Provider> {
|
||||||
searchEnabled(): boolean {
|
override searchEnabled(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
pageTitle(): string {
|
|
||||||
|
override pageTitle(): string {
|
||||||
return msg("Providers");
|
return msg("Providers");
|
||||||
}
|
}
|
||||||
pageDescription(): string {
|
|
||||||
|
override pageDescription(): string {
|
||||||
return msg("Provide support for protocols like SAML and OAuth to assigned applications.");
|
return msg("Provide support for protocols like SAML and OAuth to assigned applications.");
|
||||||
}
|
}
|
||||||
pageIcon(): string {
|
|
||||||
|
override pageIcon(): string {
|
||||||
return "pf-icon pf-icon-integration";
|
return "pf-icon pf-icon-integration";
|
||||||
}
|
}
|
||||||
|
|
||||||
checkbox = true;
|
override checkbox = true;
|
||||||
clearOnRefresh = true;
|
override clearOnRefresh = true;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
order = "name";
|
public order = "name";
|
||||||
|
|
||||||
async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
|
public searchLabel = msg("Provider name");
|
||||||
|
public searchPlaceholder = msg("Search for providers…");
|
||||||
|
|
||||||
|
override async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
|
||||||
return new ProvidersApi(DEFAULT_CONFIG).providersAllList(
|
return new ProvidersApi(DEFAULT_CONFIG).providersAllList(
|
||||||
await this.defaultEndpointConfig(),
|
await this.defaultEndpointConfig(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
columns(): TableColumn[] {
|
override columns(): TableColumn[] {
|
||||||
return [
|
return [
|
||||||
new TableColumn(msg("Name"), "name"),
|
new TableColumn(msg("Name"), "name"),
|
||||||
new TableColumn(msg("Application")),
|
new TableColumn(msg("Application")),
|
||||||
@ -62,8 +68,9 @@ export class ProviderListPage extends TablePage<Provider> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToolbarSelected(): TemplateResult {
|
override renderToolbarSelected(): TemplateResult {
|
||||||
const disabled = this.selectedElements.length < 1;
|
const disabled = this.selectedElements.length < 1;
|
||||||
|
|
||||||
return html`<ak-forms-delete-bulk
|
return html`<ak-forms-delete-bulk
|
||||||
objectLabel=${msg("Provider(s)")}
|
objectLabel=${msg("Provider(s)")}
|
||||||
.objects=${this.selectedElements}
|
.objects=${this.selectedElements}
|
||||||
@ -84,7 +91,7 @@ export class ProviderListPage extends TablePage<Provider> {
|
|||||||
</ak-forms-delete-bulk>`;
|
</ak-forms-delete-bulk>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
rowApp(item: Provider): TemplateResult {
|
#rowApp(item: Provider): TemplateResult {
|
||||||
if (item.assignedApplicationName) {
|
if (item.assignedApplicationName) {
|
||||||
return html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
|
return html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
|
||||||
${msg("Assigned to application ")}
|
${msg("Assigned to application ")}
|
||||||
@ -92,6 +99,7 @@ export class ProviderListPage extends TablePage<Provider> {
|
|||||||
>${item.assignedApplicationName}</a
|
>${item.assignedApplicationName}</a
|
||||||
>`;
|
>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.assignedBackchannelApplicationName) {
|
if (item.assignedBackchannelApplicationName) {
|
||||||
return html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
|
return html`<i class="pf-icon pf-icon-ok pf-m-success"></i>
|
||||||
${msg("Assigned to application (backchannel) ")}
|
${msg("Assigned to application (backchannel) ")}
|
||||||
@ -99,15 +107,15 @@ export class ProviderListPage extends TablePage<Provider> {
|
|||||||
>${item.assignedBackchannelApplicationName}</a
|
>${item.assignedBackchannelApplicationName}</a
|
||||||
>`;
|
>`;
|
||||||
}
|
}
|
||||||
return html`<i class="pf-icon pf-icon-warning-triangle pf-m-warning"></i> ${msg(
|
|
||||||
"Warning: Provider not assigned to any application.",
|
return html`<i aria-hidden="true" class="pf-icon pf-icon-warning-triangle pf-m-warning"></i>
|
||||||
)}`;
|
${msg("Warning: Provider not assigned to any application.")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
row(item: Provider): TemplateResult[] {
|
override row(item: Provider): TemplateResult[] {
|
||||||
return [
|
return [
|
||||||
html`<a href="#/core/providers/${item.pk}"> ${item.name} </a>`,
|
html`<a href="#/core/providers/${item.pk}"> ${item.name} </a>`,
|
||||||
this.rowApp(item),
|
this.#rowApp(item),
|
||||||
html`${item.verboseName}`,
|
html`${item.verboseName}`,
|
||||||
html`<ak-forms-modal>
|
html`<ak-forms-modal>
|
||||||
<span slot="submit"> ${msg("Update")} </span>
|
<span slot="submit"> ${msg("Update")} </span>
|
||||||
@ -120,16 +128,20 @@ export class ProviderListPage extends TablePage<Provider> {
|
|||||||
type=${item.component}
|
type=${item.component}
|
||||||
>
|
>
|
||||||
</ak-proxy-form>
|
</ak-proxy-form>
|
||||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
<button
|
||||||
|
aria-label=${msg("Edit provider")}
|
||||||
|
slot="trigger"
|
||||||
|
class="pf-c-button pf-m-plain"
|
||||||
|
>
|
||||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||||
<i class="fas fa-edit"></i>
|
<i aria-hidden="true" class="fas fa-edit"></i>
|
||||||
</pf-tooltip>
|
</pf-tooltip>
|
||||||
</button>
|
</button>
|
||||||
</ak-forms-modal>`,
|
</ak-forms-modal>`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderObjectCreate(): TemplateResult {
|
override renderObjectCreate(): TemplateResult {
|
||||||
return html`<ak-provider-wizard> </ak-provider-wizard> `;
|
return html`<ak-provider-wizard> </ak-provider-wizard> `;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,23 +25,16 @@ import { ProvidersApi, TypeCreate } from "@goauthentik/api";
|
|||||||
|
|
||||||
@customElement("ak-provider-wizard")
|
@customElement("ak-provider-wizard")
|
||||||
export class ProviderWizard extends AKElement {
|
export class ProviderWizard extends AKElement {
|
||||||
static get styles(): CSSResult[] {
|
static styles: CSSResult[] = [PFBase, PFButton];
|
||||||
return [PFBase, PFButton];
|
|
||||||
}
|
|
||||||
|
|
||||||
@property()
|
|
||||||
createText = msg("Create");
|
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
providerTypes: TypeCreate[] = [];
|
public providerTypes: TypeCreate[] = [];
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
finalHandler: () => Promise<void> = () => {
|
public finalHandler?: () => Promise<void>;
|
||||||
return Promise.resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
@query("ak-wizard")
|
@query("ak-wizard")
|
||||||
wizard?: Wizard;
|
private wizard?: Wizard;
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@ -56,9 +49,7 @@ export class ProviderWizard extends AKElement {
|
|||||||
.steps=${["initial"]}
|
.steps=${["initial"]}
|
||||||
header=${msg("New provider")}
|
header=${msg("New provider")}
|
||||||
description=${msg("Create a new provider.")}
|
description=${msg("Create a new provider.")}
|
||||||
.finalHandler=${() => {
|
.finalHandler=${this.finalHandler}
|
||||||
return this.finalHandler();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ak-wizard-page-type-create
|
<ak-wizard-page-type-create
|
||||||
name="selectProviderType"
|
name="selectProviderType"
|
||||||
@ -82,7 +73,15 @@ export class ProviderWizard extends AKElement {
|
|||||||
</ak-wizard-page-form>
|
</ak-wizard-page-form>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
|
<button
|
||||||
|
aria-label=${msg("New Provider")}
|
||||||
|
aria-description="${msg("Open the wizard to create a new provider.")}"
|
||||||
|
type="button"
|
||||||
|
slot="trigger"
|
||||||
|
class="pf-c-button pf-m-primary"
|
||||||
|
>
|
||||||
|
${msg("Create")}
|
||||||
|
</button>
|
||||||
</ak-wizard>
|
</ak-wizard>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,9 +56,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Credentials")}
|
label=${msg("Credentials")}
|
||||||
required
|
required
|
||||||
@ -181,9 +180,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("User filtering")}">
|
||||||
<span slot="header">${msg("User filtering")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="excludeUsersServiceAccount">
|
<ak-form-element-horizontal name="excludeUsersServiceAccount">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
@ -234,9 +232,8 @@ export class GoogleWorkspaceProviderFormPage extends BaseProviderForm<GoogleWork
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
|
|||||||
@ -47,7 +47,9 @@ export function renderForm(
|
|||||||
) {
|
) {
|
||||||
return html`
|
return html`
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
|
autocomplete="on"
|
||||||
name="name"
|
name="name"
|
||||||
|
placeholder=${msg("Provider name")}
|
||||||
value=${ifDefined(provider?.name)}
|
value=${ifDefined(provider?.name)}
|
||||||
label=${msg("Name")}
|
label=${msg("Name")}
|
||||||
.errorMessages=${errors?.name ?? []}
|
.errorMessages=${errors?.name ?? []}
|
||||||
@ -80,10 +82,8 @@ export function renderForm(
|
|||||||
>
|
>
|
||||||
</ak-switch-input>
|
</ak-switch-input>
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Flow settings")}">
|
||||||
<span slot="header"> ${msg("Flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
|
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Bind flow")}
|
label=${msg("Bind flow")}
|
||||||
required
|
required
|
||||||
@ -91,6 +91,7 @@ export function renderForm(
|
|||||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||||
>
|
>
|
||||||
<ak-branded-flow-search
|
<ak-branded-flow-search
|
||||||
|
label=${msg("Bind flow")}
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||||
.currentFlow=${provider?.authorizationFlow}
|
.currentFlow=${provider?.authorizationFlow}
|
||||||
.brandFlow=${brand?.flowAuthentication}
|
.brandFlow=${brand?.flowAuthentication}
|
||||||
@ -119,9 +120,8 @@ export function renderForm(
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="baseDn"
|
name="baseDn"
|
||||||
label=${msg("Base DN")}
|
label=${msg("Base DN")}
|
||||||
@ -141,6 +141,8 @@ export function renderForm(
|
|||||||
.errorMessages=${errors?.certificate ?? []}
|
.errorMessages=${errors?.certificate ?? []}
|
||||||
>
|
>
|
||||||
<ak-crypto-certificate-search
|
<ak-crypto-certificate-search
|
||||||
|
label=${msg("Certificate")}
|
||||||
|
placeholder=${msg("Select a certificate...")}
|
||||||
certificate=${ifDefined(provider?.certificate ?? nothing)}
|
certificate=${ifDefined(provider?.certificate ?? nothing)}
|
||||||
name="certificate"
|
name="certificate"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -55,9 +55,8 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
|
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -157,9 +156,8 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("User filtering")}">
|
||||||
<span slot="header">${msg("User filtering")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="excludeUsersServiceAccount">
|
<ak-form-element-horizontal name="excludeUsersServiceAccount">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
@ -210,9 +208,8 @@ export class MicrosoftEntraProviderFormPage extends BaseProviderForm<MicrosoftEn
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
|
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
|
||||||
|
|
||||||
import { css } from "lit";
|
import { CSSResult, css } from "lit";
|
||||||
import { customElement, state } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
|
|
||||||
import { ClientTypeEnum, OAuth2Provider, ProvidersApi } from "@goauthentik/api";
|
import { ClientTypeEnum, OAuth2Provider, ProvidersApi } from "@goauthentik/api";
|
||||||
@ -21,16 +21,14 @@ export async function oauth2ProvidersProvider(page = 1, search = "") {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
pagination: oauthProviders.pagination,
|
pagination: oauthProviders.pagination,
|
||||||
options: oauthProviders.results.map((provider) => providerToSelect(provider)),
|
options: oauthProviders.results.map(providerToSelect),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function oauth2ProviderSelector(instanceProviders: number[] | undefined) {
|
export function oauth2ProviderSelector(instanceProviders: number[] | undefined) {
|
||||||
if (!instanceProviders) {
|
if (!instanceProviders) {
|
||||||
return async (mappings: DualSelectPair<OAuth2Provider>[]) =>
|
return async (mappings: DualSelectPair<OAuth2Provider>[]) =>
|
||||||
mappings.filter(
|
mappings.filter(([, , , source]: DualSelectPair<OAuth2Provider>) => !source);
|
||||||
([_0, _1, _2, source]: DualSelectPair<OAuth2Provider>) => source !== undefined,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return async () => {
|
return async () => {
|
||||||
@ -57,41 +55,46 @@ export function oauth2ProviderSelector(instanceProviders: number[] | undefined)
|
|||||||
|
|
||||||
@customElement("ak-provider-oauth2-form")
|
@customElement("ak-provider-oauth2-form")
|
||||||
export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
|
export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
|
||||||
@state()
|
static styles: CSSResult[] = [
|
||||||
showClientSecret = true;
|
...super.styles,
|
||||||
|
css`
|
||||||
static get styles() {
|
|
||||||
return super.styles.concat(css`
|
|
||||||
ak-array-input {
|
ak-array-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
`);
|
`,
|
||||||
}
|
];
|
||||||
|
|
||||||
async loadInstance(pk: number): Promise<OAuth2Provider> {
|
@state()
|
||||||
|
protected showClientSecret = true;
|
||||||
|
|
||||||
|
override async loadInstance(pk: number): Promise<OAuth2Provider> {
|
||||||
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Retrieve({
|
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Retrieve({
|
||||||
id: pk,
|
id: pk,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
|
this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(data: OAuth2Provider): Promise<OAuth2Provider> {
|
override async send(data: OAuth2Provider): Promise<OAuth2Provider> {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Update({
|
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Update({
|
||||||
id: this.instance.pk,
|
id: this.instance.pk,
|
||||||
oAuth2ProviderRequest: data,
|
oAuth2ProviderRequest: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({
|
return new ProvidersApi(DEFAULT_CONFIG).providersOauth2Create({
|
||||||
oAuth2ProviderRequest: data,
|
oAuth2ProviderRequest: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderForm() {
|
override renderForm() {
|
||||||
const showClientSecretCallback = (show: boolean) => {
|
const showClientSecretCallback = (show: boolean) => {
|
||||||
this.showClientSecret = show;
|
this.showClientSecret = show;
|
||||||
};
|
};
|
||||||
|
|
||||||
return renderForm(this.instance ?? {}, [], this.showClientSecret, showClientSecretCallback);
|
return renderForm(this.instance ?? {}, [], this.showClientSecret, showClientSecretCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,7 +125,9 @@ export function renderForm(
|
|||||||
showClientSecretCallback: ShowClientSecret = defaultShowClientSecret,
|
showClientSecretCallback: ShowClientSecret = defaultShowClientSecret,
|
||||||
) {
|
) {
|
||||||
return html` <ak-text-input
|
return html` <ak-text-input
|
||||||
|
autocomplete="on"
|
||||||
name="name"
|
name="name"
|
||||||
|
placeholder=${msg("Provider name")}
|
||||||
label=${msg("Name")}
|
label=${msg("Name")}
|
||||||
value=${ifDefined(provider?.name)}
|
value=${ifDefined(provider?.name)}
|
||||||
required
|
required
|
||||||
@ -137,6 +139,8 @@ export function renderForm(
|
|||||||
required
|
required
|
||||||
>
|
>
|
||||||
<ak-flow-search
|
<ak-flow-search
|
||||||
|
label=${msg("Authorization flow")}
|
||||||
|
placeholder=${msg("Select an authorization flow...")}
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
flowType=${FlowsInstancesListDesignationEnum.Authorization}
|
||||||
.currentFlow=${provider?.authorizationFlow}
|
.currentFlow=${provider?.authorizationFlow}
|
||||||
required
|
required
|
||||||
@ -145,9 +149,8 @@ export function renderForm(
|
|||||||
${msg("Flow used when authorizing this provider.")}
|
${msg("Flow used when authorizing this provider.")}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-radio-input
|
<ak-radio-input
|
||||||
name="clientType"
|
name="clientType"
|
||||||
label=${msg("Client type")}
|
label=${msg("Client type")}
|
||||||
@ -196,6 +199,8 @@ export function renderForm(
|
|||||||
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
|
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
|
||||||
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
||||||
<ak-crypto-certificate-search
|
<ak-crypto-certificate-search
|
||||||
|
label=${msg("Signing Key")}
|
||||||
|
placeholder=${msg("Select a signing key...")}
|
||||||
certificate=${ifDefined(provider?.signingKey ?? undefined)}
|
certificate=${ifDefined(provider?.signingKey ?? undefined)}
|
||||||
singleton
|
singleton
|
||||||
></ak-crypto-certificate-search>
|
></ak-crypto-certificate-search>
|
||||||
@ -204,6 +209,8 @@ export function renderForm(
|
|||||||
<ak-form-element-horizontal label=${msg("Encryption Key")} name="encryptionKey">
|
<ak-form-element-horizontal label=${msg("Encryption Key")} name="encryptionKey">
|
||||||
<!-- NOTE: 'null' cast to 'undefined' on encryptionKey to satisfy Lit requirements -->
|
<!-- NOTE: 'null' cast to 'undefined' on encryptionKey to satisfy Lit requirements -->
|
||||||
<ak-crypto-certificate-search
|
<ak-crypto-certificate-search
|
||||||
|
label=${msg("Encryption Key")}
|
||||||
|
placeholder=${msg("Select an encryption key...")}
|
||||||
certificate=${ifDefined(provider?.encryptionKey ?? undefined)}
|
certificate=${ifDefined(provider?.encryptionKey ?? undefined)}
|
||||||
></ak-crypto-certificate-search>
|
></ak-crypto-certificate-search>
|
||||||
<p class="pf-c-form__helper-text">${msg("Key used to encrypt the tokens.")}</p>
|
<p class="pf-c-form__helper-text">${msg("Key used to encrypt the tokens.")}</p>
|
||||||
@ -211,14 +218,15 @@ export function renderForm(
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label=${msg("Advanced flow settings")}>
|
||||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
name="authenticationFlow"
|
name="authenticationFlow"
|
||||||
label=${msg("Authentication flow")}
|
label=${msg("Authentication flow")}
|
||||||
>
|
>
|
||||||
<ak-flow-search
|
<ak-flow-search
|
||||||
|
label=${msg("Authentication flow")}
|
||||||
|
placeHolder=${msg("Select an authentication flow...")}
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||||
.currentFlow=${provider?.authenticationFlow}
|
.currentFlow=${provider?.authenticationFlow}
|
||||||
></ak-flow-search>
|
></ak-flow-search>
|
||||||
@ -234,6 +242,8 @@ export function renderForm(
|
|||||||
required
|
required
|
||||||
>
|
>
|
||||||
<ak-flow-search
|
<ak-flow-search
|
||||||
|
label=${msg("Invalidation flow")}
|
||||||
|
placeHolder=${msg("Select an invalidation flow...")}
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||||
.currentFlow=${provider?.invalidationFlow}
|
.currentFlow=${provider?.invalidationFlow}
|
||||||
defaultFlowSlug="default-provider-invalidation-flow"
|
defaultFlowSlug="default-provider-invalidation-flow"
|
||||||
@ -246,9 +256,8 @@ export function renderForm(
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="accessCodeValidity"
|
name="accessCodeValidity"
|
||||||
label=${msg("Access code validity")}
|
label=${msg("Access code validity")}
|
||||||
@ -331,9 +340,8 @@ export function renderForm(
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Machine-to-Machine authentication settings")}">
|
||||||
<span slot="header">${msg("Machine-to-Machine authentication settings")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Federated OIDC Sources")}
|
label=${msg("Federated OIDC Sources")}
|
||||||
name="jwtFederationSources"
|
name="jwtFederationSources"
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
|
||||||
import { AkControlElement } from "@goauthentik/elements/AkControlElement.js";
|
import {
|
||||||
|
AkControlElement,
|
||||||
|
formatFormElementAsJSON,
|
||||||
|
} from "@goauthentik/elements/AkControlElement.js";
|
||||||
import { type Spread } from "@goauthentik/elements/types";
|
import { type Spread } from "@goauthentik/elements/types";
|
||||||
import { spread } from "@open-wc/lit-helpers";
|
import { spread } from "@open-wc/lit-helpers";
|
||||||
|
|
||||||
@ -43,9 +46,7 @@ export class OAuth2ProviderRedirectURI extends AkControlElement<RedirectURI> {
|
|||||||
controls?: HTMLInputElement[];
|
controls?: HTMLInputElement[];
|
||||||
|
|
||||||
json() {
|
json() {
|
||||||
return Object.fromEntries(
|
return formatFormElementAsJSON<RedirectURI>(this.controls);
|
||||||
Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
|
|
||||||
) as unknown as RedirectURI;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isValid() {
|
get isValid() {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
|||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import renderDescriptionList from "@goauthentik/components/DescriptionList";
|
import renderDescriptionList from "@goauthentik/components/DescriptionList";
|
||||||
import "@goauthentik/components/events/ObjectChangelog";
|
import "@goauthentik/components/events/ObjectChangelog";
|
||||||
|
import { IDGenerator } from "@goauthentik/core/id";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/EmptyState";
|
import "@goauthentik/elements/EmptyState";
|
||||||
@ -265,12 +266,16 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<form class="pf-c-form">
|
<form class="pf-c-form">
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
<label class="pf-c-form__label">
|
<label
|
||||||
|
class="pf-c-form__label"
|
||||||
|
for="${IDGenerator.elementID("providerInfo")}"
|
||||||
|
>
|
||||||
<span class="pf-c-form__label-text"
|
<span class="pf-c-form__label-text"
|
||||||
>${msg("OpenID Configuration URL")}</span
|
>${msg("OpenID Configuration URL")}</span
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="${IDGenerator.elementID("providerInfo")}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
@ -278,12 +283,16 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
<label class="pf-c-form__label">
|
<label
|
||||||
|
class="pf-c-form__label"
|
||||||
|
for="${IDGenerator.elementID("issuer")}"
|
||||||
|
>
|
||||||
<span class="pf-c-form__label-text"
|
<span class="pf-c-form__label-text"
|
||||||
>${msg("OpenID Configuration Issuer")}</span
|
>${msg("OpenID Configuration Issuer")}</span
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="${IDGenerator.elementID("issuer")}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
@ -292,12 +301,16 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
</div>
|
</div>
|
||||||
<hr class="pf-c-divider" />
|
<hr class="pf-c-divider" />
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
<label class="pf-c-form__label">
|
<label
|
||||||
|
class="pf-c-form__label"
|
||||||
|
for="${IDGenerator.elementID("authorize")}"
|
||||||
|
>
|
||||||
<span class="pf-c-form__label-text"
|
<span class="pf-c-form__label-text"
|
||||||
>${msg("Authorize URL")}</span
|
>${msg("Authorize URL")}</span
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="${IDGenerator.elementID("authorize")}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
@ -305,10 +318,14 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
<label class="pf-c-form__label">
|
<label
|
||||||
|
class="pf-c-form__label"
|
||||||
|
for="${IDGenerator.elementID("token")}"
|
||||||
|
>
|
||||||
<span class="pf-c-form__label-text">${msg("Token URL")}</span>
|
<span class="pf-c-form__label-text">${msg("Token URL")}</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="${IDGenerator.elementID("token")}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
@ -316,12 +333,16 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
<label class="pf-c-form__label">
|
<label
|
||||||
|
class="pf-c-form__label"
|
||||||
|
for="${IDGenerator.elementID("userInfo")}"
|
||||||
|
>
|
||||||
<span class="pf-c-form__label-text"
|
<span class="pf-c-form__label-text"
|
||||||
>${msg("Userinfo URL")}</span
|
>${msg("Userinfo URL")}</span
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="${IDGenerator.elementID("userInfo")}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
@ -329,10 +350,14 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
<label class="pf-c-form__label">
|
<label
|
||||||
|
class="pf-c-form__label"
|
||||||
|
for="${IDGenerator.elementID("logout")}"
|
||||||
|
>
|
||||||
<span class="pf-c-form__label-text">${msg("Logout URL")}</span>
|
<span class="pf-c-form__label-text">${msg("Logout URL")}</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="${IDGenerator.elementID("logout")}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
@ -340,10 +365,14 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
<label class="pf-c-form__label">
|
<label
|
||||||
|
class="pf-c-form__label"
|
||||||
|
for="${IDGenerator.elementID("jwks")}"
|
||||||
|
>
|
||||||
<span class="pf-c-form__label-text">${msg("JWKS URL")}</span>
|
<span class="pf-c-form__label-text">${msg("JWKS URL")}</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="${IDGenerator.elementID("jwks")}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
readonly
|
readonly
|
||||||
type="text"
|
type="text"
|
||||||
@ -389,9 +418,12 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
${renderDescriptionList(
|
${renderDescriptionList(
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
msg("Preview for user"),
|
html`<label for="${IDGenerator.elementID("preview-user")}"
|
||||||
|
>${msg("Preview for user")}</label
|
||||||
|
>`,
|
||||||
html`
|
html`
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
|
id="${IDGenerator.elementID("preview-user")}"
|
||||||
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
||||||
const args: CoreUsersListRequest = {
|
const args: CoreUsersListRequest = {
|
||||||
ordering: "username",
|
ordering: "username",
|
||||||
|
|||||||
@ -228,9 +228,8 @@ export function renderForm(
|
|||||||
input-hint="code"
|
input-hint="code"
|
||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||||
<span slot="header">${msg("Advanced protocol settings")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
|
<ak-form-element-horizontal label=${msg("Certificate")} name="certificate">
|
||||||
<ak-crypto-certificate-search
|
<ak-crypto-certificate-search
|
||||||
.certificate=${provider?.certificate}
|
.certificate=${provider?.certificate}
|
||||||
@ -273,9 +272,8 @@ ${provider?.skipPathRegex}</textarea
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Authentication settings")}">
|
||||||
<span slot="header">${msg("Authentication settings")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-switch-input
|
<ak-switch-input
|
||||||
name="interceptHeaderAuth"
|
name="interceptHeaderAuth"
|
||||||
label=${msg("Intercept header authentication")}
|
label=${msg("Intercept header authentication")}
|
||||||
@ -333,9 +331,8 @@ ${provider?.skipPathRegex}</textarea
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Authentication flow")}
|
label=${msg("Authentication flow")}
|
||||||
name="authenticationFlow"
|
name="authenticationFlow"
|
||||||
|
|||||||
@ -115,9 +115,8 @@ export class EndpointForm extends ModelForm<Endpoint, string> {
|
|||||||
selected-label="${msg("Selected User Property Mappings")}"
|
selected-label="${msg("Selected User Property Mappings")}"
|
||||||
></ak-dual-select-dynamic-selected>
|
></ak-dual-select-dynamic-selected>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced settings")}">
|
||||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Settings")} name="settings">
|
<ak-form-element-horizontal label=${msg("Settings")} name="settings">
|
||||||
<ak-codemirror
|
<ak-codemirror
|
||||||
mode="yaml"
|
mode="yaml"
|
||||||
|
|||||||
@ -115,9 +115,8 @@ export class RACProviderFormPage extends ModelForm<RACProvider, number> {
|
|||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Property mappings")}
|
label=${msg("Property mappings")}
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export function renderForm(
|
|||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="name"
|
name="name"
|
||||||
label=${msg("Name")}
|
label=${msg("Name")}
|
||||||
|
placeholder=${msg("Provider name")}
|
||||||
value=${ifDefined(provider?.name)}
|
value=${ifDefined(provider?.name)}
|
||||||
.errorMessages=${errors?.name ?? []}
|
.errorMessages=${errors?.name ?? []}
|
||||||
required
|
required
|
||||||
@ -57,6 +58,8 @@ export function renderForm(
|
|||||||
.errorMessages=${errors?.authorizationFlow ?? []}
|
.errorMessages=${errors?.authorizationFlow ?? []}
|
||||||
>
|
>
|
||||||
<ak-branded-flow-search
|
<ak-branded-flow-search
|
||||||
|
label=${msg("Authentication flow")}
|
||||||
|
placeholder=${msg("Select an authentication flow...")}
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
flowType=${FlowsInstancesListDesignationEnum.Authentication}
|
||||||
.currentFlow=${provider?.authorizationFlow}
|
.currentFlow=${provider?.authorizationFlow}
|
||||||
.brandFlow=${brand?.flowAuthentication}
|
.brandFlow=${brand?.flowAuthentication}
|
||||||
@ -73,17 +76,14 @@ export function renderForm(
|
|||||||
>
|
>
|
||||||
</ak-switch-input>
|
</ak-switch-input>
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
<ak-hidden-text-input>
|
||||||
<ak-hidden-text-input
|
name="sharedSecret" label=${msg("Shared secret")}
|
||||||
name="sharedSecret"
|
|
||||||
label=${msg("Shared secret")}
|
|
||||||
.errorMessages=${errors?.sharedSecret ?? []}
|
.errorMessages=${errors?.sharedSecret ?? []}
|
||||||
value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)}
|
value=${provider?.sharedSecret ?? randomString(128, ascii_letters + digits)}
|
||||||
required
|
required input-hint="code" ></ak-hidden-text-input
|
||||||
input-hint="code"
|
>
|
||||||
></ak-hidden-text-input>
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="clientNetworks"
|
name="clientNetworks"
|
||||||
label=${msg("Client Networks")}
|
label=${msg("Client Networks")}
|
||||||
@ -106,15 +106,16 @@ export function renderForm(
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Invalidation flow")}
|
label=${msg("Invalidation flow")}
|
||||||
name="invalidationFlow"
|
name="invalidationFlow"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<ak-flow-search
|
<ak-flow-search
|
||||||
|
label=${msg("Invalidation flow")}
|
||||||
|
placeholder=${msg("Select an invalidation flow...")}
|
||||||
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
flowType=${FlowsInstancesListDesignationEnum.Invalidation}
|
||||||
.currentFlow=${provider?.invalidationFlow}
|
.currentFlow=${provider?.invalidationFlow}
|
||||||
.errorMessages=${errors?.invalidationFlow ?? []}
|
.errorMessages=${errors?.invalidationFlow ?? []}
|
||||||
|
|||||||
@ -84,9 +84,8 @@ export function renderForm(
|
|||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="acsUrl"
|
name="acsUrl"
|
||||||
label=${msg("ACS URL")}
|
label=${msg("ACS URL")}
|
||||||
@ -122,9 +121,8 @@ export function renderForm(
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced flow settings")}">
|
||||||
<span slot="header"> ${msg("Advanced flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Authentication flow")}
|
label=${msg("Authentication flow")}
|
||||||
name="authenticationFlow"
|
name="authenticationFlow"
|
||||||
@ -157,9 +155,8 @@ export function renderForm(
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Signing Certificate")} name="signingKp">
|
<ak-form-element-horizontal label=${msg("Signing Certificate")} name="signingKp">
|
||||||
<ak-crypto-certificate-search
|
<ak-crypto-certificate-search
|
||||||
.certificate=${provider?.signingKp}
|
.certificate=${provider?.signingKp}
|
||||||
|
|||||||
@ -31,9 +31,8 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
|||||||
required
|
required
|
||||||
help=${msg("Method's display Name.")}
|
help=${msg("Method's display Name.")}
|
||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="url"
|
name="url"
|
||||||
label=${msg("URL")}
|
label=${msg("URL")}
|
||||||
@ -113,9 +112,8 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("User filtering")}">
|
||||||
<span slot="header">${msg("User filtering")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-switch-input
|
<ak-switch-input
|
||||||
name="excludeUsersServiceAccount"
|
name="excludeUsersServiceAccount"
|
||||||
label=${msg("Exclude service accounts")}
|
label=${msg("Exclude service accounts")}
|
||||||
@ -155,9 +153,8 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="propertyMappings"
|
name="propertyMappings"
|
||||||
|
|||||||
@ -57,9 +57,8 @@ export class SSFProviderFormPage extends BaseProviderForm<SSFProvider> {
|
|||||||
value=${ifDefined(provider?.name)}
|
value=${ifDefined(provider?.name)}
|
||||||
required
|
required
|
||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Signing Key")}
|
label=${msg("Signing Key")}
|
||||||
name="signingKey"
|
name="signingKey"
|
||||||
@ -93,9 +92,8 @@ export class SSFProviderFormPage extends BaseProviderForm<SSFProvider> {
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
|
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Authentication settings")}">
|
||||||
<span slot="header">${msg("Authentication settings")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("OIDC Providers")}
|
label=${msg("OIDC Providers")}
|
||||||
name="oidcAuthProviders"
|
name="oidcAuthProviders"
|
||||||
|
|||||||
@ -67,7 +67,7 @@ export class InitialPermissionsListPage extends TablePage<InitialPermissions> {
|
|||||||
</ak-forms-delete-bulk>`;
|
</ak-forms-delete-bulk>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render() {
|
||||||
return html`<ak-page-header
|
return html`<ak-page-header
|
||||||
icon=${this.pageIcon()}
|
icon=${this.pageIcon()}
|
||||||
header=${this.pageTitle()}
|
header=${this.pageTitle()}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export class RoleListPage extends TablePage<Role> {
|
|||||||
</ak-forms-delete-bulk>`;
|
</ak-forms-delete-bulk>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render() {
|
||||||
return html`<ak-page-header
|
return html`<ak-page-header
|
||||||
icon=${this.pageIcon()}
|
icon=${this.pageIcon()}
|
||||||
header=${this.pageTitle()}
|
header=${this.pageTitle()}
|
||||||
|
|||||||
@ -121,9 +121,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
"Enable this option to write password changes made in authentik back to Kerberos. Ignored if sync is disabled.",
|
"Enable this option to write password changes made in authentik back to Kerberos. Ignored if sync is disabled.",
|
||||||
)}
|
)}
|
||||||
></ak-switch-input>
|
></ak-switch-input>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Realm settings")}">
|
||||||
<span slot="header"> ${msg("Realm settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="realm"
|
name="realm"
|
||||||
label=${msg("Realm")}
|
label=${msg("Realm")}
|
||||||
@ -213,9 +212,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Sync connection settings")}">
|
||||||
<span slot="header"> ${msg("Sync connection settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("KAdmin type")}
|
label=${msg("KAdmin type")}
|
||||||
required
|
required
|
||||||
@ -276,9 +274,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("SPNEGO settings")}">
|
||||||
<span slot="header"> ${msg("SPNEGO settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="spnegoServerName"
|
name="spnegoServerName"
|
||||||
label=${msg("SPNEGO server name")}
|
label=${msg("SPNEGO server name")}
|
||||||
@ -305,9 +302,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
></ak-text-input>
|
></ak-text-input>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Kerberos Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("Kerberos Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="userPropertyMappings"
|
name="userPropertyMappings"
|
||||||
@ -344,9 +340,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Flow settings")}">
|
||||||
<span slot="header"> ${msg("Flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Authentication flow")}
|
label=${msg("Authentication flow")}
|
||||||
name="authenticationFlow"
|
name="authenticationFlow"
|
||||||
@ -377,9 +372,8 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Additional settings")}">
|
||||||
<span slot="header"> ${msg("Additional settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-text-input
|
<ak-text-input
|
||||||
name="userPathTemplate"
|
name="userPathTemplate"
|
||||||
label=${msg("User path")}
|
label=${msg("User path")}
|
||||||
|
|||||||
@ -171,9 +171,8 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Connection settings")}">
|
||||||
<span slot="header"> ${msg("Connection settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Server URI")}
|
label=${msg("Server URI")}
|
||||||
required
|
required
|
||||||
@ -277,9 +276,8 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("LDAP Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("LDAP Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="userPropertyMappings"
|
name="userPropertyMappings"
|
||||||
@ -314,9 +312,8 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Additional settings")}">
|
||||||
<span slot="header"> ${msg("Additional settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Parent Group")} name="syncParentGroup">
|
<ak-form-element-horizontal label=${msg("Parent Group")} name="syncParentGroup">
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
||||||
|
|||||||
@ -126,9 +126,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
|||||||
if (!this.providerType?.urlsCustomizable) {
|
if (!this.providerType?.urlsCustomizable) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html` <ak-form-group expanded>
|
return html` <ak-form-group open label="${msg("URL settings")}">
|
||||||
<span slot="header"> ${msg("URL settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Authorization URL")}
|
label=${msg("Authorization URL")}
|
||||||
name="authorizationUrl"
|
name="authorizationUrl"
|
||||||
@ -421,9 +420,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
|||||||
<p class="pf-c-form__helper-text">${iconHelperText}</p>
|
<p class="pf-c-form__helper-text">${iconHelperText}</p>
|
||||||
</ak-form-element-horizontal>`}
|
</ak-form-element-horizontal>`}
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Consumer key")}
|
label=${msg("Consumer key")}
|
||||||
required
|
required
|
||||||
@ -464,9 +462,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
|||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
${this.renderUrlOptions()}
|
${this.renderUrlOptions()}
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("OAuth Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("OAuth Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="userPropertyMappings"
|
name="userPropertyMappings"
|
||||||
@ -501,9 +498,8 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Flow settings")}">
|
||||||
<span slot="header"> ${msg("Flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Authentication flow")}
|
label=${msg("Authentication flow")}
|
||||||
name="authenticationFlow"
|
name="authenticationFlow"
|
||||||
|
|||||||
@ -334,9 +334,8 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
|
|||||||
/>
|
/>
|
||||||
<p class="pf-c-form__helper-text">${iconHelperText}</p>
|
<p class="pf-c-form__helper-text">${iconHelperText}</p>
|
||||||
</ak-form-element-horizontal>`}
|
</ak-form-element-horizontal>`}
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
|
<ak-form-element-horizontal label=${msg("Client ID")} required name="clientId">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -348,9 +347,8 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
|
|||||||
${this.renderSettings()}
|
${this.renderSettings()}
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Flow settings")}">
|
||||||
<span slot="header"> ${msg("Flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Authentication flow")}
|
label=${msg("Authentication flow")}
|
||||||
name="authenticationFlow"
|
name="authenticationFlow"
|
||||||
@ -381,9 +379,8 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Plex Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("Plex Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="userPropertyMappings"
|
name="userPropertyMappings"
|
||||||
|
|||||||
@ -233,9 +233,8 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
|||||||
<p class="pf-c-form__helper-text">${iconHelperText}</p>
|
<p class="pf-c-form__helper-text">${iconHelperText}</p>
|
||||||
</ak-form-element-horizontal>`}
|
</ak-form-element-horizontal>`}
|
||||||
|
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Protocol settings")}">
|
||||||
<span slot="header"> ${msg("Protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("SSO URL")} required name="ssoUrl">
|
<ak-form-element-horizontal label=${msg("SSO URL")} required name="ssoUrl">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -321,9 +320,8 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="allowIdpInitiated">
|
<ak-form-element-horizontal name="allowIdpInitiated">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
@ -493,9 +491,8 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("SAML Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("SAML Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="userPropertyMappings"
|
name="userPropertyMappings"
|
||||||
@ -530,9 +527,8 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Flow settings")}">
|
||||||
<span slot="header"> ${msg("Flow settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Pre-authentication flow")}
|
label=${msg("Pre-authentication flow")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -68,9 +68,8 @@ export class SCIMSourceForm extends BaseSourceForm<SCIMSource> {
|
|||||||
<label class="pf-c-check__label"> ${msg("Enabled")} </label>
|
<label class="pf-c-check__label"> ${msg("Enabled")} </label>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("SCIM Attribute mapping")}">
|
||||||
<span slot="header"> ${msg("SCIM Attribute mapping")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User Property Mappings")}
|
label=${msg("User Property Mappings")}
|
||||||
name="userPropertyMappings"
|
name="userPropertyMappings"
|
||||||
@ -105,9 +104,8 @@ export class SCIMSourceForm extends BaseSourceForm<SCIMSource> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced protocol settings")}">
|
||||||
<span slot="header"> ${msg("Advanced protocol settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("User path")} name="userPathTemplate">
|
<ak-form-element-horizontal label=${msg("User path")} name="userPathTemplate">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@ -80,9 +80,8 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Duo Auth API")}">
|
||||||
<span slot="header"> ${msg("Duo Auth API")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Integration key")}
|
label=${msg("Integration key")}
|
||||||
required
|
required
|
||||||
@ -104,15 +103,13 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
|
|||||||
></ak-secret-text-input>
|
></ak-secret-text-input>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group
|
||||||
<span slot="header">${msg("Duo Admin API (optional)")}</span>
|
label=${msg("Duo Admin API (optional)")}
|
||||||
<span slot="description">
|
description="${msg(
|
||||||
${msg(
|
`When using a Duo MFA, Access or Beyond plan, an Admin API application can be created. This will allow authentik to import devices automatically.`,
|
||||||
`When using a Duo MFA, Access or Beyond plan, an Admin API application can be created.
|
)}"
|
||||||
This will allow authentik to import devices automatically.`,
|
>
|
||||||
)}
|
<div class="pf-c-form">
|
||||||
</span>
|
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Integration key")}
|
label=${msg("Integration key")}
|
||||||
name="adminIntegrationKey"
|
name="adminIntegrationKey"
|
||||||
@ -133,9 +130,8 @@ export class AuthenticatorDuoStageForm extends BaseStageForm<AuthenticatorDuoSta
|
|||||||
></ak-secret-text-input>
|
></ak-secret-text-input>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Configuration flow")}
|
label=${msg("Configuration flow")}
|
||||||
name="configureFlow"
|
name="configureFlow"
|
||||||
|
|||||||
@ -50,9 +50,8 @@ export class AuthenticatorEmailStageForm extends BaseStageForm<AuthenticatorEmai
|
|||||||
if (!this.showConnectionSettings) {
|
if (!this.showConnectionSettings) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`<ak-form-group expanded>
|
return html`<ak-form-group open label="${msg("Connection settings")}">
|
||||||
<span slot="header"> ${msg("Connection settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("SMTP Host")} required name="host">
|
<ak-form-element-horizontal label=${msg("SMTP Host")} required name="host">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -191,9 +190,8 @@ export class AuthenticatorEmailStageForm extends BaseStageForm<AuthenticatorEmai
|
|||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${this.renderConnectionSettings()}
|
${this.renderConnectionSettings()}
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Subject")} required name="subject">
|
<ak-form-element-horizontal label=${msg("Subject")} required name="subject">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@ -55,9 +55,8 @@ export class AuthenticatorEndpointGDTCStageForm extends BaseStageForm<Authentica
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Google Verified Access API")}">
|
||||||
<span slot="header"> ${msg("Google Verified Access API")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Credentials")}
|
label=${msg("Credentials")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -222,9 +222,8 @@ export class AuthenticatorSMSStageForm extends BaseStageForm<AuthenticatorSMSSta
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Provider")} required name="provider">
|
<ak-form-element-horizontal label=${msg("Provider")} required name="provider">
|
||||||
<select
|
<select
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
|
|||||||
@ -67,9 +67,8 @@ export class AuthenticatorStaticStageForm extends BaseStageForm<AuthenticatorSta
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Token count")}
|
label=${msg("Token count")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -69,9 +69,8 @@ export class AuthenticatorTOTPStageForm extends BaseStageForm<AuthenticatorTOTPS
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Digits")} required name="digits">
|
<ak-form-element-horizontal label=${msg("Digits")} required name="digits">
|
||||||
<select name="users" class="pf-c-form-control">
|
<select name="users" class="pf-c-form-control">
|
||||||
<option
|
<option
|
||||||
|
|||||||
@ -95,9 +95,8 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Device classes")}
|
label=${msg("Device classes")}
|
||||||
required
|
required
|
||||||
@ -208,9 +207,8 @@ export class AuthenticatorValidateStageForm extends BaseStageForm<AuthenticatorV
|
|||||||
: html``}
|
: html``}
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("WebAuthn-specific settings")}">
|
||||||
<span slot="header"> ${msg("WebAuthn-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("WebAuthn User verification")}
|
label=${msg("WebAuthn User verification")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -77,9 +77,8 @@ export class AuthenticatorWebAuthnStageForm extends BaseStageForm<AuthenticatorW
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("User verification")}
|
label=${msg("User verification")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -47,9 +47,8 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Public Key")}
|
label=${msg("Public Key")}
|
||||||
required
|
required
|
||||||
@ -126,9 +125,8 @@ export class CaptchaStageForm extends BaseStageForm<CaptchaStage> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Advanced settings")}">
|
||||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("JS URL")} required name="jsUrl">
|
<ak-form-element-horizontal label=${msg("JS URL")} required name="jsUrl">
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
|
|||||||
@ -53,9 +53,8 @@ export class ConsentStageForm extends BaseStageForm<ConsentStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
||||||
<select
|
<select
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
|
|||||||
@ -44,9 +44,8 @@ export class DenyStageForm extends BaseStageForm<DenyStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Deny message")} name="denyMessage">
|
<ak-form-element-horizontal label=${msg("Deny message")} name="denyMessage">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@ -47,9 +47,8 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
|
|||||||
if (!this.showConnectionSettings) {
|
if (!this.showConnectionSettings) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`<ak-form-group>
|
return html`<ak-form-group label="${msg("Connection settings")}">
|
||||||
<span slot="header"> ${msg("Connection settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("SMTP Host")} required name="host">
|
<ak-form-element-horizontal label=${msg("SMTP Host")} required name="host">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -146,9 +145,8 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="activateUserOnSuccess">
|
<ak-form-element-horizontal name="activateUserOnSuccess">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -84,9 +84,8 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("User fields")} name="userFields">
|
<ak-form-element-horizontal label=${msg("User fields")} name="userFields">
|
||||||
<ak-checkbox-group
|
<ak-checkbox-group
|
||||||
class="user-field-select"
|
class="user-field-select"
|
||||||
@ -193,9 +192,8 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
|
|||||||
></ak-switch-input>
|
></ak-switch-input>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Source settings")}">
|
||||||
<span slot="header"> ${msg("Source settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Sources")} required name="sources">
|
<ak-form-element-horizontal label=${msg("Sources")} required name="sources">
|
||||||
<ak-dual-select-dynamic-selected
|
<ak-dual-select-dynamic-selected
|
||||||
.provider=${sourcesProvider}
|
.provider=${sourcesProvider}
|
||||||
@ -231,9 +229,8 @@ export class IdentificationStageForm extends BaseStageForm<IdentificationStage>
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
<ak-form-group>
|
<ak-form-group label="${msg("Flow settings")}">
|
||||||
<span slot="header">${msg("Flow settings")}</span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Passwordless flow")}
|
label=${msg("Passwordless flow")}
|
||||||
name="passwordlessFlow"
|
name="passwordlessFlow"
|
||||||
|
|||||||
@ -172,7 +172,7 @@ export class InvitationListPage extends TablePage<Invitation> {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render() {
|
||||||
return html`<ak-page-header
|
return html`<ak-page-header
|
||||||
icon=${this.pageIcon()}
|
icon=${this.pageIcon()}
|
||||||
header=${this.pageTitle()}
|
header=${this.pageTitle()}
|
||||||
|
|||||||
@ -41,9 +41,8 @@ export class InvitationStageForm extends BaseStageForm<InvitationStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="continueFlowWithoutInvitation">
|
<ak-form-element-horizontal name="continueFlowWithoutInvitation">
|
||||||
<label class="pf-c-switch">
|
<label class="pf-c-switch">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -53,9 +53,8 @@ export class MTLSStageForm extends BaseStageForm<MutualTLSStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
||||||
<ak-radio
|
<ak-radio
|
||||||
.options=${[
|
.options=${[
|
||||||
|
|||||||
@ -82,9 +82,8 @@ export class PasswordStageForm extends BaseStageForm<PasswordStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Backends")} required name="backends">
|
<ak-form-element-horizontal label=${msg("Backends")} required name="backends">
|
||||||
<ak-checkbox-group
|
<ak-checkbox-group
|
||||||
class="user-field-select"
|
class="user-field-select"
|
||||||
|
|||||||
@ -55,9 +55,8 @@ export class PromptStageForm extends BaseStageForm<PromptStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Fields")} required name="fields">
|
<ak-form-element-horizontal label=${msg("Fields")} required name="fields">
|
||||||
<ak-dual-select-dynamic-selected
|
<ak-dual-select-dynamic-selected
|
||||||
.provider=${promptFieldsProvider}
|
.provider=${promptFieldsProvider}
|
||||||
|
|||||||
@ -56,9 +56,8 @@ export class RedirectStageForm extends BaseStageForm<RedirectStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
|
||||||
<select
|
<select
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
|
|||||||
@ -41,9 +41,8 @@ export class UserLoginStageForm extends BaseStageForm<UserLoginStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Session duration")}
|
label=${msg("Session duration")}
|
||||||
required
|
required
|
||||||
|
|||||||
@ -55,9 +55,8 @@ export class UserWriteStageForm extends BaseStageForm<UserWriteStage> {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-group expanded>
|
<ak-form-group open label="${msg("Stage-specific settings")}">
|
||||||
<span slot="header"> ${msg("Stage-specific settings")} </span>
|
<div class="pf-c-form">
|
||||||
<div slot="body" class="pf-c-form">
|
|
||||||
<ak-form-element-horizontal name="userCreationMode">
|
<ak-form-element-horizontal name="userCreationMode">
|
||||||
<ak-radio
|
<ak-radio
|
||||||
.options=${[
|
.options=${[
|
||||||
|
|||||||
@ -399,7 +399,7 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSidebarBefore(): TemplateResult {
|
protected renderSidebarBefore(): TemplateResult {
|
||||||
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
|
return html`<div class="pf-c-sidebar__panel pf-m-width-25">
|
||||||
<div class="pf-c-card">
|
<div class="pf-c-card">
|
||||||
<div class="pf-c-card__title">${msg("User folders")}</div>
|
<div class="pf-c-card__title">${msg("User folders")}</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user