mirror of
https://github.com/goauthentik/authentik
synced 2026-05-15 11:26:31 +02:00
Compare commits
16 Commits
website/sw
...
modal-revi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
847c5daebb | ||
|
|
ca05471485 | ||
|
|
545175a62f | ||
|
|
fc5b7c952c | ||
|
|
b86538f5e2 | ||
|
|
c38a1f4641 | ||
|
|
50ef836504 | ||
|
|
47b609043a | ||
|
|
5a352800ae | ||
|
|
b833da704b | ||
|
|
26b2cf9cde | ||
|
|
e995fa55cf | ||
|
|
b2f1533867 | ||
|
|
6afe08d53c | ||
|
|
0c8daa2ed0 | ||
|
|
8798d8133f |
@@ -162,6 +162,12 @@ export class FormFixture extends PageFixture {
|
||||
await expect(group, `Field "${groupName}" should be visible`).toBeVisible();
|
||||
const control = parent.getByRole("radio", { name: fieldName });
|
||||
|
||||
// await control.scrollIntoViewIfNeeded();
|
||||
// await expect(control, `Option "${fieldName}" should be visible`).toBeVisible();
|
||||
|
||||
// TODO: setChecked seems to be buggy with our custom radio buttons, so we use click for now. Revisit this if we switch to native radios.
|
||||
// await control.click();
|
||||
// await expect(control, `Option "${fieldName}" should be checked`).toBeChecked();
|
||||
await control.setChecked(true);
|
||||
};
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"#styles/*.css": "./src/styles/*.css",
|
||||
"#styles/*": "./src/styles/*.js",
|
||||
"#common/*": "./src/common/*.js",
|
||||
"#elements/dialogs": "./src/elements/dialogs/index.js",
|
||||
"#elements/*.css": "./src/elements/*.css",
|
||||
"#elements/*": "./src/elements/*.js",
|
||||
"#components/*.css": "./src/components/*.css",
|
||||
|
||||
@@ -36,6 +36,7 @@ export default defineConfig({
|
||||
testIdAttribute: "data-test-id",
|
||||
baseURL,
|
||||
trace: "on-first-retry",
|
||||
colorScheme: "dark",
|
||||
launchOptions: {
|
||||
logger: {
|
||||
isEnabled() {
|
||||
|
||||
@@ -43,13 +43,16 @@ export class FooterLinkInput extends AKControlElement<FooterLink> {
|
||||
@queryAll(".ak-form-control")
|
||||
controls?: HTMLInputElement[];
|
||||
|
||||
json() {
|
||||
@property({ type: String })
|
||||
public name: string | null = null;
|
||||
|
||||
toJSON(): FooterLink {
|
||||
return Object.fromEntries(
|
||||
Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
|
||||
) as unknown as FooterLink;
|
||||
}
|
||||
|
||||
get isValid() {
|
||||
get valid() {
|
||||
const href = this.json()?.href ?? "";
|
||||
return hasLegalScheme(href) && URL.canParse(href);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@ import { akFooterLinkInput, IFooterLinkInput } from "./AdminSettingsFooterLinks.
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { Form } from "#elements/forms/Form";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { AdminApi, FooterLink, Settings, SettingsRequest } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, CSSResult, html, TemplateResult } from "lit";
|
||||
import { css, CSSResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@@ -56,7 +57,20 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
public override submitLabel = msg("Save changes");
|
||||
|
||||
public override renderHeader() {
|
||||
return html`<div class="ak-c-form__header">
|
||||
<h2 class="pf-c-title pf-m-2xl sr-only">${msg("Edit Settings")}</h2>
|
||||
<div part="form-actions">${this.renderSubmitButton()}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
public override renderActions(): SlottedTemplateResult {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override renderForm(): SlottedTemplateResult {
|
||||
const { settings } = this;
|
||||
|
||||
return html`
|
||||
|
||||
@@ -66,19 +66,18 @@ export class AdminSettingsPage extends AKElement {
|
||||
if (!this.settings) return nothing;
|
||||
|
||||
return html`
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||
<main class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-admin-settings-form
|
||||
id="form"
|
||||
.settings=${this.settings}
|
||||
action-label=${msg("Update settings")}
|
||||
@ak-form-submitted=${{ handleEvent: this.#refresh, passive: true }}
|
||||
>
|
||||
</ak-admin-settings-form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const metadata: Meta<FooterLinkInput> = {
|
||||
return;
|
||||
}
|
||||
const target = event.target as FooterLinkInput;
|
||||
messages!.innerText = `${JSON.stringify(target.json(), null, 2)}\n\nValid: ${target.isValid ? "Yes" : "No"}`;
|
||||
messages!.innerText = `${JSON.stringify(target.json(), null, 2)}\n\nValid: ${target.valid ? "Yes" : "No"}`;
|
||||
});
|
||||
}, 250);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const metadata: Meta<IArrayInput<unknown>> = {
|
||||
return;
|
||||
}
|
||||
const target = event.target as FooterLinkInput;
|
||||
messages!.innerText = `${JSON.stringify(target.json(), null, 2)}\n\nValid: ${target.isValid ? "Yes" : "No"}`;
|
||||
messages!.innerText = `${JSON.stringify(target.json(), null, 2)}\n\nValid: ${target.valid ? "Yes" : "No"}`;
|
||||
});
|
||||
}, 250);
|
||||
|
||||
|
||||
@@ -1,72 +1,51 @@
|
||||
import "#elements/EmptyState";
|
||||
import "#elements/ak-progress-bar";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { globalAK } from "#common/global";
|
||||
|
||||
import { asInvoker } from "#elements/dialogs";
|
||||
import { AKModal } from "#elements/dialogs/ak-modal";
|
||||
import { WithBrandConfig } from "#elements/mixins/branding";
|
||||
import { WithLicenseSummary } from "#elements/mixins/license";
|
||||
import { AKModal } from "#elements/modals/ak-modal";
|
||||
import { asInvoker } from "#elements/modals/utils";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { ThemedImage } from "#elements/utils/images";
|
||||
|
||||
import { AdminApi, CapabilitiesEnum, LicenseSummaryStatusEnum } from "@goauthentik/api";
|
||||
import {
|
||||
AdminApi,
|
||||
CapabilitiesEnum,
|
||||
LicenseSummaryStatusEnum,
|
||||
SystemInfo,
|
||||
Version,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, html, TemplateResult } from "lit";
|
||||
import { css, html } from "lit";
|
||||
import { ref } from "lit-html/directives/ref.js";
|
||||
import { styleMap } from "lit-html/directives/style-map.js";
|
||||
import { until } from "lit-html/directives/until.js";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import PFAbout from "@patternfly/patternfly/components/AboutModalBox/about-modal-box.css";
|
||||
|
||||
const DEFAULT_BRAND_IMAGE = "/static/dist/assets/images/flow_background.jpg";
|
||||
|
||||
type AboutEntry = [label: string, content: string | TemplateResult];
|
||||
type AboutEntry = [label: string, content?: SlottedTemplateResult];
|
||||
|
||||
async function fetchAboutDetails(): Promise<AboutEntry[]> {
|
||||
const api = new AdminApi(DEFAULT_CONFIG);
|
||||
|
||||
const [status, version] = await Promise.all([
|
||||
api.adminSystemRetrieve(),
|
||||
api.adminVersionRetrieve(),
|
||||
]);
|
||||
|
||||
let build: string | TemplateResult = msg("Release");
|
||||
|
||||
if (globalAK().config.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
build = msg("Development");
|
||||
} else if (version.buildHash) {
|
||||
build = html`<a
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/goauthentik/authentik/commit/${version.buildHash}"
|
||||
target="_blank"
|
||||
>${version.buildHash}</a
|
||||
>`;
|
||||
}
|
||||
|
||||
return [
|
||||
[msg("Version"), version.versionCurrent],
|
||||
[msg("UI Version"), import.meta.env.AK_VERSION],
|
||||
[msg("Build"), build],
|
||||
[msg("Python version"), status.runtime.pythonVersion],
|
||||
[msg("Platform"), status.runtime.platform],
|
||||
[msg("Kernel"), status.runtime.uname],
|
||||
[
|
||||
msg("OpenSSL"),
|
||||
`${status.runtime.opensslVersion} ${status.runtime.opensslFipsEnabled ? "FIPS" : ""}`,
|
||||
],
|
||||
];
|
||||
function renderEntry([label, content = null]: AboutEntry): SlottedTemplateResult {
|
||||
return html`<dt>${label}</dt>
|
||||
<dd>${content === null ? msg("Loading...") : content}</dd>`;
|
||||
}
|
||||
|
||||
@customElement("ak-about-modal")
|
||||
export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
||||
public static override formatARIALabel = () => msg("About authentik");
|
||||
public override formatARIALabel = () => msg("About authentik");
|
||||
|
||||
public static hostStyles = [
|
||||
...AKModal.hostStyles,
|
||||
css`
|
||||
dialog.ak-c-modal:has(ak-about-modal) {
|
||||
--ak-c-modal--BackgroundColor: var(--pf-global--palette--black-900);
|
||||
--ak-c-modal--BorderColor: var(--pf-global--palette--black-600);
|
||||
.ak-c-dialog:has(ak-about-modal) {
|
||||
--ak-c-dialog--BackgroundColor: var(--pf-global--palette--black-900);
|
||||
--ak-c-dialog--BorderColor: var(--pf-global--palette--black-600);
|
||||
}
|
||||
`,
|
||||
];
|
||||
@@ -80,7 +59,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
||||
}
|
||||
|
||||
.pf-c-about-modal-box {
|
||||
--pf-c-about-modal-box--BackgroundColor: var(--ak-c-modal--BackgroundColor);
|
||||
--pf-c-about-modal-box--BackgroundColor: var(--ak-c-dialog--BackgroundColor);
|
||||
width: unset;
|
||||
height: 100%;
|
||||
max-height: unset;
|
||||
@@ -89,6 +68,17 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
||||
position: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
[part="brand"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[part="loading-bar"] {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset-block-start: 0;
|
||||
inset-inline: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -96,26 +86,100 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
||||
|
||||
public static open = asInvoker(AboutModal);
|
||||
|
||||
@state()
|
||||
protected entries: AboutEntry[] | null = null;
|
||||
#api = new AdminApi(DEFAULT_CONFIG);
|
||||
|
||||
public refresh() {
|
||||
return fetchAboutDetails().then((entries) => {
|
||||
this.entries = entries;
|
||||
protected canDebug = globalAK().config.capabilities.includes(CapabilitiesEnum.CanDebug);
|
||||
|
||||
@state()
|
||||
protected version: Version | null = null;
|
||||
|
||||
@state()
|
||||
protected systemInfo: SystemInfo | null = null;
|
||||
|
||||
@state()
|
||||
protected refreshPromise: Promise<[Version, SystemInfo]> | null = null;
|
||||
|
||||
public refresh = (): void => {
|
||||
const versionPromise = this.#api.adminVersionRetrieve();
|
||||
const systemInfoPromise = this.#api.adminSystemRetrieve();
|
||||
|
||||
this.refreshPromise = Promise.all([versionPromise, systemInfoPromise]).then((result) => {
|
||||
this.version = result[0];
|
||||
this.systemInfo = result[1];
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
protected renderVersionInfo = () => {
|
||||
const { version } = this;
|
||||
|
||||
let build: SlottedTemplateResult = null;
|
||||
|
||||
if (this.canDebug) {
|
||||
build = msg("Development");
|
||||
} else if (version?.buildHash) {
|
||||
build = html`<a
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/goauthentik/authentik/commit/${version.buildHash}"
|
||||
target="_blank"
|
||||
>${version.buildHash}</a
|
||||
>`;
|
||||
} else if (version) {
|
||||
build = msg("Release");
|
||||
}
|
||||
|
||||
const entries: AboutEntry[] = [
|
||||
[msg("Server Version"), version?.versionCurrent],
|
||||
[msg("Build"), build],
|
||||
];
|
||||
|
||||
return entries.map(renderEntry);
|
||||
};
|
||||
|
||||
protected renderSystemInfo = () => {
|
||||
const { runtime } = this.systemInfo || {};
|
||||
|
||||
const sslLabel = runtime
|
||||
? `${runtime.opensslVersion} ${runtime.opensslFipsEnabled ? "FIPS" : ""}`
|
||||
: null;
|
||||
|
||||
const entries: AboutEntry[] = [
|
||||
[msg("Python version"), runtime?.pythonVersion],
|
||||
[msg("Platform"), runtime?.platform],
|
||||
[msg("OpenSSL"), sslLabel],
|
||||
[
|
||||
msg("Kernel"),
|
||||
runtime?.uname ?? html`<div style="min-height: 3em;">${msg("Loading...")}</div>`,
|
||||
],
|
||||
];
|
||||
|
||||
return entries.map(renderEntry);
|
||||
};
|
||||
|
||||
//#region Renderers
|
||||
|
||||
protected override renderCloseButton() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected renderLoadingBar(): SlottedTemplateResult {
|
||||
return until(
|
||||
this.refreshPromise?.then(() => null),
|
||||
html`<ak-progress-bar
|
||||
part="loading-bar"
|
||||
indeterminate
|
||||
?inert=${!!this.systemInfo && !!this.version}
|
||||
label=${msg("Loading")}
|
||||
></ak-progress-bar>`,
|
||||
);
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
let product = this.brandingTitle;
|
||||
|
||||
@@ -129,6 +193,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
||||
style=${styleMap({
|
||||
"--pf-c-about-modal-box__hero--sm--BackgroundImage": `url(${DEFAULT_BRAND_IMAGE})`,
|
||||
})}
|
||||
part="box"
|
||||
>
|
||||
<div class="pf-c-about-modal-box__close">
|
||||
<button
|
||||
@@ -140,7 +205,8 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__brand">
|
||||
<div class="pf-c-about-modal-box__brand" part="brand">
|
||||
${this.renderLoadingBar()}
|
||||
${ThemedImage({
|
||||
src: this.brandingFavicon,
|
||||
alt: msg("authentik Logo"),
|
||||
@@ -149,21 +215,25 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
||||
themedUrls: this.brandingFaviconThemedUrls,
|
||||
})}
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-4xl" id="modal-title">${product}</h1>
|
||||
<div class="pf-c-about-modal-box__header" part="header">
|
||||
<h1 class="pf-c-title pf-m-4xl" id="modal-title" part="title">${product}</h1>
|
||||
</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__body">
|
||||
<div class="pf-c-content">
|
||||
${this.entries
|
||||
? html`<dl>
|
||||
${this.entries.map(([label, value]) => {
|
||||
return html`<dt>${label}</dt>
|
||||
<dd>${value}</dd>`;
|
||||
})}
|
||||
</dl>`
|
||||
: html`<ak-empty-state loading></ak-empty-state>`}
|
||||
<dl>
|
||||
<dt>${msg("UI Version")}</dt>
|
||||
<dd>${import.meta.env.AK_VERSION}</dd>
|
||||
${until(
|
||||
this.refreshPromise?.then(this.renderVersionInfo),
|
||||
this.renderVersionInfo(),
|
||||
)}
|
||||
${until(
|
||||
this.refreshPromise?.then(this.renderSystemInfo),
|
||||
this.renderSystemInfo(),
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<p class="pf-c-about-modal-box__strapline"></p>
|
||||
|
||||
@@ -4,6 +4,7 @@ import "#elements/sidebar/Sidebar";
|
||||
import "#elements/sidebar/SidebarItem";
|
||||
import "#elements/router/RouterOutlet";
|
||||
import "#elements/commands/ak-command-palette";
|
||||
import "#elements/commands/ak-command-palette-user-modal";
|
||||
|
||||
import {
|
||||
createAdminSidebarEnterpriseEntries,
|
||||
@@ -24,10 +25,10 @@ import {
|
||||
PaletteCommandNamespace,
|
||||
} from "#elements/commands/shared";
|
||||
import { listen } from "#elements/decorators/listen";
|
||||
import { renderDialog } from "#elements/dialogs";
|
||||
import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
|
||||
import { WithNotifications } from "#elements/mixins/notifications";
|
||||
import { canAccessAdmin, WithSession } from "#elements/mixins/session";
|
||||
import { renderDialog } from "#elements/modals/utils";
|
||||
import { AKDrawerChangeEvent } from "#elements/notifications/events";
|
||||
import {
|
||||
DrawerState,
|
||||
@@ -36,6 +37,7 @@ import {
|
||||
renderNotificationDrawerPanel,
|
||||
} from "#elements/notifications/utils";
|
||||
import { navigate } from "#elements/router/RouterOutlet";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import Styles from "#admin/ak-interface-admin.css";
|
||||
import { ROUTES } from "#admin/Routes";
|
||||
@@ -46,6 +48,7 @@ import { LOCALE_STATUS_EVENT, LocaleStatusEventDetail, msg } from "@lit/localize
|
||||
import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, eventOptions, property, state } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
import { guard } from "lit/directives/guard.js";
|
||||
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
@@ -266,40 +269,7 @@ export class AdminInterface extends WithCapabilitiesConfig(
|
||||
<i aria-hidden="true" class="fas fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
slot="nav-buttons"
|
||||
@click=${this.commandPalette.showListener}
|
||||
class="pf-c-button pf-m-plain command-palette-trigger"
|
||||
aria-label=${msg("Open Command Palette", {
|
||||
id: "command-palette-trigger-label",
|
||||
desc: "Label for the button that opens the command palette",
|
||||
})}
|
||||
>
|
||||
<pf-tooltip position="top-end">
|
||||
<div slot="content" class="ak-tooltip__content--inline">
|
||||
${msg("Open Command Palette", {
|
||||
id: "command-palette-trigger-tooltip",
|
||||
desc: "Tooltip for the button that opens the command palette",
|
||||
})}
|
||||
<div class="ak-c-kbd"><kbd>Ctrl</kbd> + <kbd>K</kbd></div>
|
||||
</div>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
class="ak-c-vector-icon"
|
||||
role="img"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M26 4.01H6a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-20a2 2 0 0 0-2-2m0 2v4H6v-4Zm-20 20v-14h20v14Z"
|
||||
/>
|
||||
<path
|
||||
d="m10.76 16.18 2.82 2.83-2.82 2.83 1.41 1.41 4.24-4.24-4.24-4.24z"
|
||||
/>
|
||||
</svg>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
${this.renderCommandPaletteButton()}
|
||||
<ak-version-banner></ak-version-banner>
|
||||
<ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||
</ak-page-navbar>
|
||||
@@ -344,6 +314,47 @@ export class AdminInterface extends WithCapabilitiesConfig(
|
||||
${this.commandPalette}`;
|
||||
}
|
||||
|
||||
protected renderCommandPaletteButton(): SlottedTemplateResult {
|
||||
return guard([this.commandPalette.showListener], () => {
|
||||
const macOS = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
||||
|
||||
const primaryModifierKey = macOS ? "⌘" : "Ctrl";
|
||||
|
||||
return html`<button
|
||||
slot="nav-buttons"
|
||||
@click=${this.commandPalette.showListener}
|
||||
class="pf-c-button pf-m-plain command-palette-trigger"
|
||||
aria-label=${msg("Open Command Palette", {
|
||||
id: "command-palette-trigger-label",
|
||||
desc: "Label for the button that opens the command palette",
|
||||
})}
|
||||
>
|
||||
<pf-tooltip position="top-end">
|
||||
<div slot="content" class="ak-tooltip__content--inline">
|
||||
${msg("Open Command Palette", {
|
||||
id: "command-palette-trigger-tooltip",
|
||||
desc: "Tooltip for the button that opens the command palette",
|
||||
})}
|
||||
<div class="ak-c-kbd"><kbd>${primaryModifierKey}</kbd> + <kbd>K</kbd></div>
|
||||
</div>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
class="ak-c-vector-icon"
|
||||
role="img"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M26 4.01H6a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-20a2 2 0 0 0-2-2m0 2v4H6v-4Zm-20 20v-14h20v14Z"
|
||||
/>
|
||||
<path d="m10.76 16.18 2.82 2.83-2.82 2.83 1.41 1.41 4.24-4.24-4.24-4.24z" />
|
||||
</svg>
|
||||
</pf-tooltip>
|
||||
</button>`;
|
||||
});
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
|
||||
import { Application, CoreApi, Provider, UsageEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, nothing, TemplateResult } from "lit";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@@ -40,8 +40,8 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Application, string>) {
|
||||
#api = new CoreApi(DEFAULT_CONFIG);
|
||||
|
||||
public override entitySingular = msg("Application");
|
||||
public override entityPlural = msg("Applications");
|
||||
public static override verboseName = msg("Application");
|
||||
public static override verboseNamePlural = msg("Applications");
|
||||
|
||||
protected override async loadInstance(pk: string): Promise<Application> {
|
||||
const app = await this.#api.coreApplicationsRetrieve({
|
||||
@@ -54,7 +54,7 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
public provider?: number;
|
||||
public provider: number | null = null;
|
||||
|
||||
@state()
|
||||
protected backchannelProviders: Provider[] = [];
|
||||
@@ -115,10 +115,12 @@ export class ApplicationForm extends WithCapabilitiesConfig(ModelForm<Applicatio
|
||||
);
|
||||
const providerFromInstance = this.instance?.provider;
|
||||
const providerValue = providerFromInstance ?? this.provider;
|
||||
const providerPrefilled = !this.instance && this.provider !== undefined;
|
||||
const providerPrefilled = !this.instance && this.provider !== null;
|
||||
|
||||
return html`
|
||||
${this.instance ? nothing : html`<ak-alert level="pf-m-info">${alertMsg}</ak-alert>`}
|
||||
${this.instance || this.provider
|
||||
? null
|
||||
: html`<ak-alert level="pf-m-info">${alertMsg}</ak-alert>`}
|
||||
<ak-text-input
|
||||
name="name"
|
||||
autocomplete="off"
|
||||
|
||||
@@ -5,12 +5,13 @@ import "#elements/ak-mdx/ak-mdx";
|
||||
import "#elements/buttons/SpinnerButton/ak-spinner-button";
|
||||
import "#elements/forms/DeleteBulkForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
import "#elements/modals/ak-modal";
|
||||
import "#elements/dialogs/ak-modal";
|
||||
import "#admin/applications/ApplicationForm";
|
||||
import "#admin/applications/ApplicationWizardHint";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEditButton } from "#elements/dialogs";
|
||||
import { WithBrandConfig } from "#elements/mixins/branding";
|
||||
import { getURLParam } from "#elements/router/RouteMatch";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
@@ -20,7 +21,7 @@ import { ifPresent } from "#elements/utils/attributes";
|
||||
|
||||
import { ApplicationForm } from "#admin/applications/ApplicationForm";
|
||||
import Styles from "#admin/applications/ApplicationListPage.css";
|
||||
import { AkApplicationWizard } from "#admin/applications/wizard/ak-application-wizard";
|
||||
import { AKApplicationWizard } from "#admin/applications/wizard/ak-application-wizard";
|
||||
|
||||
import { Application, CoreApi, PoliciesApi } from "@goauthentik/api";
|
||||
|
||||
@@ -45,6 +46,9 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
|
||||
protected override searchEnabled = true;
|
||||
public pageTitle = msg("Applications");
|
||||
public searchLabel = msg("Applications search");
|
||||
public searchPlaceholder = msg("Search for application by name, group or provider...");
|
||||
|
||||
public get pageDescription() {
|
||||
return msg(
|
||||
str`External applications that use ${this.brandingTitle} as an identity provider via protocols like OAuth2 and SAML. All applications are shown here, even ones you cannot access.`,
|
||||
@@ -69,7 +73,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
super.firstUpdated(changed);
|
||||
|
||||
if (getURLParam("createWizard", false)) {
|
||||
AkApplicationWizard.showModal();
|
||||
AKApplicationWizard.showModal();
|
||||
} else if (getURLParam("createForm", false)) {
|
||||
ApplicationForm.showModal();
|
||||
}
|
||||
@@ -138,16 +142,8 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
</a>`
|
||||
: html`-`,
|
||||
html`${item.providerObj?.verboseName || msg("-")}`,
|
||||
html`<div>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
aria-label=${msg(str`Edit "${item.name}"`)}
|
||||
${ApplicationForm.asEditModalInvoker(item.slug)}
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(ApplicationForm, item.slug)}
|
||||
${item.launchUrl
|
||||
? html`<a
|
||||
href=${item.launchUrl}
|
||||
@@ -166,17 +162,28 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
|
||||
protected override renderObjectCreate(): TemplateResult {
|
||||
return html`<ak-dropdown class="pf-c-dropdown">
|
||||
<button
|
||||
class="pf-c-button pf-m-primary pf-c-dropdown__toggle"
|
||||
type="button"
|
||||
id="new-application-toggle"
|
||||
aria-haspopup="menu"
|
||||
aria-controls="new-application-menu"
|
||||
tabindex="0"
|
||||
>
|
||||
<span class="pf-c-dropdown__toggle-text">${msg("New Application")}</span>
|
||||
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="pf-c-dropdown__toggle pf-m-primary pf-m-split-button pf-m-action">
|
||||
<button
|
||||
class="pf-c-dropdown__toggle-button"
|
||||
type="button"
|
||||
${AKApplicationWizard.asModalInvoker()}
|
||||
>
|
||||
${msg("New Application")}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="pf-c-dropdown__toggle-button"
|
||||
type="button"
|
||||
id="new-application-toggle"
|
||||
aria-haspopup="menu"
|
||||
aria-controls="new-application-menu"
|
||||
tabindex="0"
|
||||
aria-label=${msg("New Application options")}
|
||||
>
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<menu
|
||||
class="pf-c-dropdown__menu"
|
||||
hidden
|
||||
@@ -189,7 +196,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
type="button"
|
||||
role="menuitem"
|
||||
class="pf-c-dropdown__menu-item"
|
||||
${AkApplicationWizard.asModalInvoker()}
|
||||
${AKApplicationWizard.asModalInvoker()}
|
||||
aria-description=${msg(
|
||||
"Opens the new application wizard, which will guide you through creating a new application with an existing provider.",
|
||||
)}
|
||||
|
||||
@@ -5,8 +5,8 @@ import "#elements/chips/ChipGroup";
|
||||
import "#elements/forms/Form";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { renderModal } from "#elements/dialogs";
|
||||
import { AKFormSubmitEvent } from "#elements/forms/Form";
|
||||
import { renderModal } from "#elements/modals/utils";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { Provider } from "@goauthentik/api";
|
||||
@@ -65,7 +65,7 @@ export class AkBackchannelProvidersInput extends AKElement {
|
||||
return renderModal(html`
|
||||
<ak-form
|
||||
headline=${this.label}
|
||||
action-label=${msg("Confirm")}
|
||||
submit-label=${msg("Confirm")}
|
||||
@submit=${(event: AKFormSubmitEvent<Provider[]>) => {
|
||||
const providers = event.target.toJSON();
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ export class ApplicationEntitlementsPage extends Table<ApplicationEntitlement> {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
protected override renderEmpty(): SlottedTemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon="pf-icon-module"
|
||||
><span>${msg("No app entitlements created.")}</span>
|
||||
|
||||
@@ -10,8 +10,8 @@ import { WizardStep } from "#components/ak-wizard/WizardStep";
|
||||
|
||||
import { ApplicationWizardStyles } from "#admin/applications/wizard/ApplicationWizardFormStepStyles.styles";
|
||||
import {
|
||||
type ApplicationWizardState,
|
||||
type ApplicationWizardStateUpdate,
|
||||
type ApplicationWizardContext,
|
||||
type ApplicationWizardContextUpdate,
|
||||
} from "#admin/applications/wizard/steps/providers/shared";
|
||||
|
||||
import { ApplicationRequest } from "@goauthentik/api";
|
||||
@@ -19,6 +19,12 @@ import { ApplicationRequest } from "@goauthentik/api";
|
||||
import { msg } from "@lit/localize";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
export interface ApplicationDispatchInit {
|
||||
update?: ApplicationWizardContextUpdate | null;
|
||||
destination?: string | null;
|
||||
details?: NavigationEventInit | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for application wizard steps. Provides common functionality such as form handling and wizard state management.
|
||||
*
|
||||
@@ -28,7 +34,7 @@ export abstract class ApplicationWizardStep<T = Partial<ApplicationRequest>> ext
|
||||
static styles = [...WizardStep.styles, ...ApplicationWizardStyles];
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public wizard!: ApplicationWizardState;
|
||||
public wizard!: ApplicationWizardContext;
|
||||
|
||||
protected override wizardTitle = msg("New application");
|
||||
protected override wizardDescription = msg(
|
||||
@@ -78,19 +84,15 @@ export abstract class ApplicationWizardStep<T = Partial<ApplicationRequest>> ext
|
||||
|
||||
// This pattern became visible during development, and the order is important: wizard updating
|
||||
// and validation must complete before navigation is attempted.
|
||||
public handleUpdate(
|
||||
update?: ApplicationWizardStateUpdate,
|
||||
destination?: string,
|
||||
enable?: NavigationEventInit,
|
||||
) {
|
||||
public dispatchEvents({ update, destination, details }: ApplicationDispatchInit): void {
|
||||
// Inform ApplicationWizard of content state
|
||||
if (update) {
|
||||
this.dispatchEvent(new WizardUpdateEvent(update));
|
||||
}
|
||||
|
||||
// Inform WizardStepManager of steps state
|
||||
if (destination || enable) {
|
||||
this.dispatchEvent(new WizardNavigationEvent(destination, enable));
|
||||
if (destination || details) {
|
||||
this.dispatchEvent(new WizardNavigationEvent(details, destination));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import "#components/ak-wizard/ak-wizard-steps";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-application-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-bindings-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-edit-binding-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-provider-choice-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-provider-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-submit-step";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { assertEveryPresent } from "#common/utils";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
|
||||
import { WizardUpdateEvent } from "#components/ak-wizard/events";
|
||||
|
||||
import { applicationWizardProvidersContext } from "#admin/applications/wizard/ContextIdentity";
|
||||
import {
|
||||
type ApplicationWizardState,
|
||||
type ApplicationWizardStateUpdate,
|
||||
} from "#admin/applications/wizard/steps/providers/shared";
|
||||
|
||||
import type { TypeCreate } from "@goauthentik/api";
|
||||
import { ProviderModelEnum, ProvidersApi, ProxyMode } from "@goauthentik/api";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
const freshWizardState = (): ApplicationWizardState => ({
|
||||
providerModel: "",
|
||||
currentBinding: -1,
|
||||
app: {},
|
||||
provider: {},
|
||||
proxyMode: ProxyMode.Proxy,
|
||||
bindings: [],
|
||||
errors: {},
|
||||
});
|
||||
|
||||
type ExtractProviderName<T extends string> = T extends `${string}.${infer Name}` ? Name : never;
|
||||
|
||||
type ProviderModelNameEnum = ExtractProviderName<ProviderModelEnum> | "samlproviderimportmodel";
|
||||
|
||||
export const providerTypePriority: ProviderModelNameEnum[] = [
|
||||
"oauth2provider",
|
||||
"samlprovider",
|
||||
"samlproviderimportmodel",
|
||||
"racprovider",
|
||||
"proxyprovider",
|
||||
"radiusprovider",
|
||||
"ldapprovider",
|
||||
"scimprovider",
|
||||
"wsfederationprovider",
|
||||
];
|
||||
|
||||
@customElement("ak-application-wizard-main")
|
||||
export class AkApplicationWizardMain extends AKElement {
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
return this;
|
||||
}
|
||||
|
||||
@state()
|
||||
protected wizard: ApplicationWizardState = freshWizardState();
|
||||
|
||||
protected wizardProviderProvider = new ContextProvider(this, {
|
||||
context: applicationWizardProvidersContext,
|
||||
initialValue: [],
|
||||
});
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener(WizardUpdateEvent.eventName, this.handleUpdate);
|
||||
}
|
||||
|
||||
public override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((providerTypes) => {
|
||||
const providerNameToProviderMap = new Map(
|
||||
providerTypes.map((providerType) => [providerType.modelName, providerType]),
|
||||
);
|
||||
const providersInOrder = providerTypePriority.map((name) =>
|
||||
providerNameToProviderMap.get(name),
|
||||
);
|
||||
assertEveryPresent<TypeCreate>(
|
||||
providersInOrder,
|
||||
"Provider priority list includes name for which no provider model was returned.",
|
||||
);
|
||||
this.wizardProviderProvider.setValue(providersInOrder);
|
||||
});
|
||||
}
|
||||
|
||||
// This is the actual top of the Wizard; so this is where we accept the update information and
|
||||
// incorporate it into the wizard.
|
||||
handleUpdate(ev: WizardUpdateEvent<ApplicationWizardStateUpdate>) {
|
||||
ev.stopPropagation();
|
||||
const update = ev.content;
|
||||
|
||||
if (typeof update !== "undefined") {
|
||||
this.wizard = {
|
||||
...this.wizard,
|
||||
...update,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<ak-wizard-steps>
|
||||
<ak-application-wizard-application-step
|
||||
slot="application"
|
||||
.wizard=${this.wizard}
|
||||
></ak-application-wizard-application-step>
|
||||
<ak-application-wizard-provider-choice-step
|
||||
slot="provider-choice"
|
||||
.wizard=${this.wizard}
|
||||
></ak-application-wizard-provider-choice-step>
|
||||
<ak-application-wizard-provider-step
|
||||
slot="provider"
|
||||
.wizard=${this.wizard}
|
||||
></ak-application-wizard-provider-step>
|
||||
<ak-application-wizard-bindings-step
|
||||
slot="bindings"
|
||||
.wizard=${this.wizard}
|
||||
></ak-application-wizard-bindings-step>
|
||||
<ak-application-wizard-edit-binding-step
|
||||
slot="edit-binding"
|
||||
.wizard=${this.wizard}
|
||||
></ak-application-wizard-edit-binding-step>
|
||||
<ak-application-wizard-submit-step
|
||||
slot="submit"
|
||||
.wizard=${this.wizard}
|
||||
></ak-application-wizard-submit-step>
|
||||
</ak-wizard-steps>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-application-wizard-main": AkApplicationWizardMain;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,147 @@
|
||||
import "#admin/applications/wizard/ak-application-wizard-main";
|
||||
import "#components/ak-wizard/ak-wizard-steps";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-application-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-bindings-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-edit-binding-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-provider-choice-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-provider-step";
|
||||
import "#admin/applications/wizard/steps/ak-application-wizard-submit-step";
|
||||
|
||||
import { AKModal } from "#elements/modals/ak-modal";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { assertEveryPresent } from "#common/utils";
|
||||
|
||||
import { WizardCloseEvent } from "#components/ak-wizard/events";
|
||||
import { listen } from "#elements/decorators/listen";
|
||||
import { CreateWizard } from "#elements/wizard/CreateWizard";
|
||||
|
||||
import { WizardUpdateEvent } from "#components/ak-wizard/events";
|
||||
|
||||
import { applicationWizardProvidersContext } from "#admin/applications/wizard/ContextIdentity";
|
||||
import {
|
||||
type ApplicationWizardContext,
|
||||
type ApplicationWizardContextUpdate,
|
||||
} from "#admin/applications/wizard/steps/providers/shared";
|
||||
|
||||
import type { TypeCreate } from "@goauthentik/api";
|
||||
import { ProviderModelEnum, ProvidersApi, ProxyMode } from "@goauthentik/api";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, CSSResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
const createWizardContextValue = (): ApplicationWizardContext => ({
|
||||
providerModel: "",
|
||||
currentBinding: -1,
|
||||
app: {},
|
||||
provider: {},
|
||||
proxyMode: ProxyMode.Proxy,
|
||||
bindings: [],
|
||||
errors: {},
|
||||
});
|
||||
|
||||
type ExtractProviderName<T extends string> = T extends `${string}.${infer Name}` ? Name : never;
|
||||
|
||||
type ProviderModelNameEnum = ExtractProviderName<ProviderModelEnum> | "samlproviderimportmodel";
|
||||
|
||||
export const providerTypePriority: ProviderModelNameEnum[] = [
|
||||
"oauth2provider",
|
||||
"samlprovider",
|
||||
"samlproviderimportmodel",
|
||||
"racprovider",
|
||||
"proxyprovider",
|
||||
"radiusprovider",
|
||||
"ldapprovider",
|
||||
"scimprovider",
|
||||
"wsfederationprovider",
|
||||
];
|
||||
|
||||
@customElement("ak-application-wizard")
|
||||
export class AkApplicationWizard extends AKModal {
|
||||
public static override formatARIALabel?(): string {
|
||||
return msg("New Application Wizard");
|
||||
export class AKApplicationWizard extends CreateWizard {
|
||||
#api = new ProvidersApi(DEFAULT_CONFIG);
|
||||
|
||||
public static override verboseName = msg("Application");
|
||||
public static override verboseNamePlural = msg("Applications");
|
||||
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
return this;
|
||||
}
|
||||
|
||||
public static override styles: CSSResult[] = [
|
||||
...super.styles,
|
||||
css`
|
||||
[part="main"] {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
@state()
|
||||
protected context: ApplicationWizardContext = createWizardContextValue();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
protected wizardProviderProvider = new ContextProvider(this, {
|
||||
context: applicationWizardProvidersContext,
|
||||
initialValue: [],
|
||||
});
|
||||
|
||||
this.addEventListener(WizardCloseEvent.eventName, this.closeListener);
|
||||
public override refresh = (): Promise<void> => {
|
||||
return this.#api.providersAllTypesList().then((providerTypes) => {
|
||||
const providerNameToProviderMap = new Map(
|
||||
providerTypes.map((providerType) => [providerType.modelName, providerType]),
|
||||
);
|
||||
|
||||
const providersInOrder = providerTypePriority.map((name) =>
|
||||
providerNameToProviderMap.get(name),
|
||||
);
|
||||
|
||||
assertEveryPresent<TypeCreate>(
|
||||
providersInOrder,
|
||||
"Provider priority list includes name for which no provider model was returned.",
|
||||
);
|
||||
|
||||
this.wizardProviderProvider.setValue(providersInOrder);
|
||||
});
|
||||
};
|
||||
|
||||
// This is the actual top of the Wizard; so this is where we accept the update information and
|
||||
// incorporate it into the wizard.
|
||||
/**
|
||||
* Handles updates to the wizard context, which are emitted by the individual steps when their data changes.
|
||||
*/
|
||||
@listen(WizardUpdateEvent)
|
||||
handleUpdate(ev: WizardUpdateEvent<ApplicationWizardContextUpdate>) {
|
||||
ev.stopPropagation();
|
||||
const update = ev.content;
|
||||
|
||||
if (update) {
|
||||
this.context = {
|
||||
...this.context,
|
||||
...update,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected renderCloseButton(): SlottedTemplateResult {
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<ak-application-wizard-main part="main"></ak-application-wizard-main>`;
|
||||
protected override render() {
|
||||
return html`<ak-wizard-steps>
|
||||
<ak-application-wizard-application-step
|
||||
slot="application"
|
||||
.wizard=${this.context}
|
||||
></ak-application-wizard-application-step>
|
||||
<ak-application-wizard-provider-choice-step
|
||||
slot="provider-choice"
|
||||
.wizard=${this.context}
|
||||
></ak-application-wizard-provider-choice-step>
|
||||
<ak-application-wizard-provider-step
|
||||
slot="provider"
|
||||
.wizard=${this.context}
|
||||
></ak-application-wizard-provider-step>
|
||||
<ak-application-wizard-bindings-step
|
||||
slot="bindings"
|
||||
.wizard=${this.context}
|
||||
></ak-application-wizard-bindings-step>
|
||||
<ak-application-wizard-edit-binding-step
|
||||
slot="edit-binding"
|
||||
.wizard=${this.context}
|
||||
></ak-application-wizard-edit-binding-step>
|
||||
<ak-application-wizard-submit-step
|
||||
slot="submit"
|
||||
.wizard=${this.context}
|
||||
></ak-application-wizard-submit-step>
|
||||
</ak-wizard-steps>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-application-wizard": AkApplicationWizard;
|
||||
"ak-application-wizard": AKApplicationWizard;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { AKElement } from "#elements/Base";
|
||||
|
||||
import { css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
||||
|
||||
@customElement("ak-wizard-title")
|
||||
export class AkWizardTitle extends AKElement {
|
||||
static styles = [
|
||||
PFContent,
|
||||
PFTitle,
|
||||
css`
|
||||
.ak-bottom-spacing {
|
||||
padding-bottom: var(--pf-global--spacer--lg);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`<div class="ak-bottom-spacing pf-c-content">
|
||||
<h3><slot></slot></h3>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default AkWizardTitle;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-wizard-title": AkWizardTitle;
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,8 @@ const renderSAMLImportOverview: ProviderOverview<ProvidersSamlImportMetadataCrea
|
||||
provider,
|
||||
) => {
|
||||
return renderSummary("SAML", provider.name, [
|
||||
[msg("Authorization flow"), provider.authorizationFlow ?? "-"],
|
||||
[msg("Invalidation flow"), provider.invalidationFlow ?? "-"],
|
||||
[msg("Authorization Flow"), provider.authorizationFlow ?? "-"],
|
||||
[msg("Invalidation Flow"), provider.invalidationFlow ?? "-"],
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -153,7 +153,7 @@ const renderOAuth2Overview: ProviderOverview<OAuth2Provider> = (provider) => {
|
||||
const label = provider.clientType ? clientTypeToLabel[provider.clientType]() : "";
|
||||
|
||||
return renderSummary("OAuth2", provider.name, [
|
||||
[msg("Client type"), label],
|
||||
[msg("Client Type"), label],
|
||||
[msg("Client ID"), provider.clientId],
|
||||
[msg("Redirect URIs"), formatRedirectUris(provider.redirectUris)],
|
||||
]);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
import "#components/ak-file-search-input";
|
||||
import "#components/ak-radio-input";
|
||||
import "#components/ak-slug-input";
|
||||
@@ -16,7 +15,7 @@ import { type NavigableButton, type WizardButton } from "#components/ak-wizard/s
|
||||
|
||||
import { ApplicationWizardStep } from "#admin/applications/wizard/ApplicationWizardStep";
|
||||
import {
|
||||
ApplicationWizardStateUpdate,
|
||||
ApplicationWizardContextUpdate,
|
||||
WizardValidationRecord,
|
||||
} from "#admin/applications/wizard/steps/providers/shared";
|
||||
import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
|
||||
@@ -61,13 +60,11 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
: (this.wizard.errors?.app?.[name] ?? this.wizard.errors?.app?.[snakeCase(name)] ?? []);
|
||||
}
|
||||
|
||||
get buttons(): WizardButton[] {
|
||||
return [
|
||||
// ---
|
||||
{ kind: "cancel" },
|
||||
{ kind: "next", destination: "provider-choice" },
|
||||
];
|
||||
}
|
||||
protected buttons: WizardButton[] = [
|
||||
// ---
|
||||
{ kind: "cancel" },
|
||||
{ kind: "next", destination: "provider-choice" },
|
||||
];
|
||||
|
||||
get valid() {
|
||||
this.errors = new Map();
|
||||
@@ -95,7 +92,7 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
}
|
||||
|
||||
if (!this.valid) {
|
||||
this.handleEnabling({
|
||||
this.dispatchNavigationEvent({
|
||||
disabled: ["provider-choice", "provider", "bindings", "submit"],
|
||||
});
|
||||
|
||||
@@ -104,24 +101,26 @@ export class ApplicationWizardApplicationStep extends ApplicationWizardStep {
|
||||
|
||||
const app = { ...this.formValues };
|
||||
|
||||
const payload: ApplicationWizardStateUpdate = {
|
||||
const update: ApplicationWizardContextUpdate = {
|
||||
app,
|
||||
errors: omitKeys(this.wizard.errors, "app"),
|
||||
};
|
||||
|
||||
if (!this.wizard.provider?.name?.trim() && app.name) {
|
||||
payload.provider = {
|
||||
update.provider = {
|
||||
name: `Provider for ${app.name}`,
|
||||
};
|
||||
}
|
||||
|
||||
this.handleUpdate(payload, button.destination, {
|
||||
enable: "provider-choice",
|
||||
return this.dispatchEvents({
|
||||
update,
|
||||
destination: button.destination,
|
||||
details: { enable: "provider-choice" },
|
||||
});
|
||||
}
|
||||
|
||||
protected renderForm(app: Partial<ApplicationRequest>, errors: WizardValidationRecord = {}) {
|
||||
return html` <ak-wizard-title>${msg("Configure the Application")}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${msg("Configure the Application")}</h3>
|
||||
<form id="applicationform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
<ak-text-input
|
||||
name="name"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "#elements/EmptyState";
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
import "#components/ak-radio-input";
|
||||
import "#components/ak-slug-input";
|
||||
import "#components/ak-status-label";
|
||||
@@ -40,13 +39,11 @@ const COLUMNS = [
|
||||
export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
||||
label = msg("Configure Bindings");
|
||||
|
||||
get buttons(): WizardButton[] {
|
||||
return [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "provider" },
|
||||
{ kind: "next", destination: "submit" },
|
||||
];
|
||||
}
|
||||
protected buttons: WizardButton[] = [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "provider" },
|
||||
{ kind: "next", destination: "submit" },
|
||||
];
|
||||
|
||||
@query("ak-select-table")
|
||||
selectTable!: SelectTable;
|
||||
@@ -64,6 +61,7 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
||||
get bindingsAsColumns() {
|
||||
return this.wizard.bindings.map((binding, index) => {
|
||||
const { order, enabled, timeout } = binding;
|
||||
|
||||
const isSet = P.union(P.string.minLength(1), P.number);
|
||||
const policy = match(binding)
|
||||
.with({ policy: isSet }, (v) => msg(str`Policy ${v.policyObj?.name}`))
|
||||
@@ -89,26 +87,32 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
||||
// TODO Fix those dispatches so that we handle them here, in this component, and *choose* how to
|
||||
// forward them.
|
||||
onBindingEvent(binding?: number) {
|
||||
this.handleUpdate({ currentBinding: binding ?? -1 }, "edit-binding", {
|
||||
enable: "edit-binding",
|
||||
this.dispatchEvents({
|
||||
update: { currentBinding: binding ?? -1 },
|
||||
destination: "edit-binding",
|
||||
details: { enable: "edit-binding" },
|
||||
});
|
||||
}
|
||||
|
||||
onDeleteBindings() {
|
||||
protected onDeleteBindings() {
|
||||
const toDelete = this.selectTable
|
||||
.json()
|
||||
.map((i) => (typeof i === "string" ? parseInt(i, 10) : i));
|
||||
const bindings = this.wizard.bindings.filter((binding, index) => !toDelete.includes(index));
|
||||
this.handleUpdate({ bindings }, "bindings");
|
||||
|
||||
return this.dispatchEvents({
|
||||
update: { bindings },
|
||||
destination: "bindings",
|
||||
});
|
||||
}
|
||||
|
||||
renderEmptyCollection() {
|
||||
return html`<ak-wizard-title
|
||||
>${msg("Configure Policy/User/Group Bindings")}</ak-wizard-title
|
||||
>
|
||||
<h6 class="pf-c-title pf-m-md">
|
||||
protected renderEmptyCollection() {
|
||||
return html`<h3 class="pf-c-wizard__main-title">
|
||||
${msg("Configure Policy/User/Group Bindings")}
|
||||
</h3>
|
||||
<h4 class="pf-c-title pf-m-md">
|
||||
${msg("These policies control which users can access this application.")}
|
||||
</h6>
|
||||
</h4>
|
||||
<div class="pf-c-card">
|
||||
<ak-application-wizard-bindings-toolbar
|
||||
@clickNew=${() => this.onBindingEvent()}
|
||||
@@ -136,11 +140,11 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderCollection() {
|
||||
return html` <ak-wizard-title>${msg("Configure Policy Bindings")}</ak-wizard-title>
|
||||
<h6 class="pf-c-title pf-m-md">
|
||||
protected renderCollection() {
|
||||
return html`<h3 class="pf-c-wizard__main-title">${msg("Configure Policy Bindings")}</h3>
|
||||
<h4 class="pf-c-title pf-m-md">
|
||||
${msg("These policies control which users can access this application.")}
|
||||
</h6>
|
||||
</h4>
|
||||
<ak-application-wizard-bindings-toolbar
|
||||
@clickNew=${() => this.onBindingEvent()}
|
||||
@clickDelete=${() => this.onDeleteBindings()}
|
||||
@@ -155,7 +159,7 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
|
||||
></ak-select-table>`;
|
||||
}
|
||||
|
||||
renderMain() {
|
||||
protected renderMain() {
|
||||
if ((this.wizard.bindings ?? []).length === 0) {
|
||||
return this.renderEmptyCollection();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "#components/ak-number-input";
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
import "#components/ak-radio-input";
|
||||
import "#components/ak-switch-input";
|
||||
import "#components/ak-text-input";
|
||||
@@ -50,17 +49,15 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
|
||||
@state()
|
||||
policyGroupUser: PolicyBindingCheckTarget = PolicyBindingCheckTarget.Policy;
|
||||
|
||||
instanceId = -1;
|
||||
protected instanceId = -1;
|
||||
|
||||
instance?: PolicyBinding;
|
||||
protected instance: PolicyBinding | null = null;
|
||||
|
||||
get buttons(): WizardButton[] {
|
||||
return [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "next", label: msg("Save Binding"), destination: "bindings" },
|
||||
{ kind: "back", destination: "bindings" },
|
||||
];
|
||||
}
|
||||
protected buttons: WizardButton[] = [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "bindings" },
|
||||
{ kind: "next", label: msg("Save Binding"), destination: "bindings" },
|
||||
];
|
||||
|
||||
public override handleButton(button: NavigableButton) {
|
||||
if (button.kind === "next") {
|
||||
@@ -84,12 +81,14 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
|
||||
}
|
||||
|
||||
this.instanceId = -1;
|
||||
this.handleUpdate({ bindings }, "bindings");
|
||||
|
||||
return;
|
||||
return this.dispatchEvents({
|
||||
update: { bindings },
|
||||
destination: "bindings",
|
||||
});
|
||||
}
|
||||
|
||||
super.handleButton(button);
|
||||
return super.handleButton(button);
|
||||
}
|
||||
|
||||
// The search select configurations for the three different types of fetches that we care about,
|
||||
@@ -153,7 +152,7 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
|
||||
}
|
||||
}
|
||||
|
||||
renderSearch(title: string, policyKind: PolicyBindingCheckTarget) {
|
||||
protected renderSearch(title: string, policyKind: PolicyBindingCheckTarget) {
|
||||
if (policyKind !== this.policyGroupUser) {
|
||||
return nothing;
|
||||
}
|
||||
@@ -163,12 +162,15 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
|
||||
.config=${this.searchSelectConfigs(policyKind)}
|
||||
class="policy-search-select"
|
||||
blankable
|
||||
placeholder=${msg(`Select a ${title}...`)}
|
||||
></ak-search-select-ez>
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
renderForm(instance?: PolicyBinding) {
|
||||
return html`<ak-wizard-title>${msg("Create a Policy/User/Group Binding")}</ak-wizard-title>
|
||||
protected renderForm(instance?: PolicyBinding | null) {
|
||||
return html`<h3 class="pf-c-wizard__main-title">
|
||||
${msg("Create a Policy/User/Group Binding")}
|
||||
</h3>
|
||||
<form id="bindingform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
||||
<div class="pf-c-card__body">
|
||||
@@ -222,16 +224,18 @@ export class ApplicationWizardEditBindingStep extends ApplicationWizardStep<Poli
|
||||
</form>`;
|
||||
}
|
||||
|
||||
renderMain() {
|
||||
protected renderMain() {
|
||||
if (!(this.wizard.bindings && this.wizard.errors)) {
|
||||
throw new Error("Application Step received uninitialized wizard context.");
|
||||
}
|
||||
|
||||
const currentBinding = this.wizard.currentBinding ?? -1;
|
||||
|
||||
if (this.instanceId !== currentBinding) {
|
||||
this.instanceId = currentBinding;
|
||||
this.instance =
|
||||
this.instanceId === -1 ? undefined : this.wizard.bindings[this.instanceId];
|
||||
this.instance = this.instanceId === -1 ? null : this.wizard.bindings[this.instanceId];
|
||||
}
|
||||
|
||||
return this.renderForm(this.instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
import "#elements/EmptyState";
|
||||
import "#elements/forms/FormGroup";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
@@ -6,8 +5,8 @@ import "#elements/wizard/TypeCreateWizardPage";
|
||||
|
||||
import { applicationWizardProvidersContext } from "../ContextIdentity.js";
|
||||
|
||||
import { bound } from "#elements/decorators/bound";
|
||||
import { WithLicenseSummary } from "#elements/mixins/license";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { TypeCreateWizardPageLayouts } from "#elements/wizard/TypeCreateWizardPage";
|
||||
|
||||
import type { NavigableButton, WizardButton } from "#components/ak-wizard/shared";
|
||||
@@ -19,6 +18,7 @@ import type { TypeCreate } from "@goauthentik/api";
|
||||
import { consume } from "@lit/context";
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { guard } from "lit-html/directives/guard.js";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
/**
|
||||
@@ -30,65 +30,69 @@ export class ApplicationWizardProviderChoiceStep extends WithLicenseSummary(Appl
|
||||
label = msg("Choose a Provider");
|
||||
|
||||
@state()
|
||||
failureMessage = "";
|
||||
protected failureMessage = "";
|
||||
|
||||
@consume({ context: applicationWizardProvidersContext, subscribe: true })
|
||||
public providerModelsList!: TypeCreate[];
|
||||
|
||||
get buttons(): WizardButton[] {
|
||||
return [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "application" },
|
||||
{ kind: "next", destination: "provider" },
|
||||
];
|
||||
}
|
||||
protected buttons: WizardButton[] = [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "application" },
|
||||
{ kind: "next", destination: "provider" },
|
||||
];
|
||||
|
||||
public override handleButton(button: NavigableButton) {
|
||||
this.failureMessage = "";
|
||||
|
||||
if (button.kind === "next") {
|
||||
if (!this.wizard.providerModel) {
|
||||
this.failureMessage = msg("Please choose a provider type before proceeding.");
|
||||
this.handleEnabling({ disabled: ["provider", "bindings", "submit"] });
|
||||
this.dispatchNavigationEvent({ disabled: ["provider", "bindings", "submit"] });
|
||||
|
||||
return;
|
||||
}
|
||||
this.handleUpdate(undefined, button.destination, { enable: "provider" });
|
||||
return;
|
||||
|
||||
return this.dispatchEvents({
|
||||
destination: button.destination,
|
||||
details: { enable: "provider" },
|
||||
});
|
||||
}
|
||||
super.handleButton(button);
|
||||
|
||||
return super.handleButton(button);
|
||||
}
|
||||
|
||||
@bound
|
||||
onSelect(ev: CustomEvent<TypeCreate>) {
|
||||
ev.stopPropagation();
|
||||
const detail: TypeCreate = ev.detail;
|
||||
this.handleUpdate({ providerModel: detail.modelName });
|
||||
}
|
||||
protected typeSelectListener = (event: CustomEvent<TypeCreate>) => {
|
||||
return this.dispatchEvents({
|
||||
update: {
|
||||
...this.wizard,
|
||||
providerModel: event.detail.modelName,
|
||||
},
|
||||
details: { enable: "provider" },
|
||||
});
|
||||
};
|
||||
|
||||
renderMain() {
|
||||
const selectedTypes = this.providerModelsList.filter(
|
||||
(t) => t.modelName === this.wizard.providerModel,
|
||||
);
|
||||
protected renderMain(): SlottedTemplateResult {
|
||||
const { providerModelsList } = this;
|
||||
|
||||
return this.providerModelsList.length > 0
|
||||
? html` <ak-wizard-title>${msg("Choose a Provider Type")}</ak-wizard-title>
|
||||
<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-wizard-page-type-create
|
||||
.types=${this.providerModelsList}
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
.selectedType=${selectedTypes.length > 0 ? selectedTypes[0] : undefined}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
this.handleUpdate(
|
||||
{
|
||||
...this.wizard,
|
||||
providerModel: ev.detail.modelName,
|
||||
},
|
||||
undefined,
|
||||
{ enable: "provider" },
|
||||
);
|
||||
}}
|
||||
></ak-wizard-page-type-create>
|
||||
</form>`
|
||||
: html`<ak-empty-state default-label></ak-empty-state>`;
|
||||
return guard([providerModelsList], () => {
|
||||
if (!providerModelsList.length) {
|
||||
return html`<ak-empty-state default-label></ak-empty-state>`;
|
||||
}
|
||||
|
||||
const selectedTypes = providerModelsList.filter(
|
||||
(t) => t.modelName === this.wizard.providerModel,
|
||||
);
|
||||
|
||||
return html`<h3 class="pf-c-wizard__main-title">${msg("Choose a Provider Type")}</h3>
|
||||
<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-wizard-page-type-create
|
||||
.types=${providerModelsList}
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
.selectedType=${selectedTypes.length > 0 ? selectedTypes[0] : null}
|
||||
@ak-type-create-select=${this.typeSelectListener}
|
||||
></ak-wizard-page-type-create>
|
||||
</form>`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,11 +75,12 @@ export class ApplicationWizardProviderStep extends ApplicationWizardStep {
|
||||
public override handleButton(button: NavigableButton) {
|
||||
if (button.kind === "next") {
|
||||
if (!this.valid) {
|
||||
this.handleEnabling({
|
||||
this.dispatchNavigationEvent({
|
||||
disabled: ["bindings", "submit"],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
provider: {
|
||||
...this.formValues,
|
||||
@@ -87,21 +88,22 @@ export class ApplicationWizardProviderStep extends ApplicationWizardStep {
|
||||
},
|
||||
errors: omitKeys(this.wizard.errors, "provider"),
|
||||
};
|
||||
this.handleUpdate(payload, button.destination, {
|
||||
enable: ["bindings", "submit"],
|
||||
|
||||
return this.dispatchEvents({
|
||||
update: payload,
|
||||
destination: button.destination,
|
||||
details: { enable: ["bindings", "submit"] },
|
||||
});
|
||||
return;
|
||||
}
|
||||
super.handleButton(button);
|
||||
|
||||
return super.handleButton(button);
|
||||
}
|
||||
|
||||
get buttons(): WizardButton[] {
|
||||
return [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "provider-choice" },
|
||||
{ kind: "next", destination: "bindings" },
|
||||
];
|
||||
}
|
||||
protected buttons: WizardButton[] = [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "provider-choice" },
|
||||
{ kind: "next", destination: "bindings" },
|
||||
];
|
||||
|
||||
renderMain() {
|
||||
if (!this.wizard.providerModel) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { EVENT_REFRESH } from "#common/constants";
|
||||
import { parseAPIResponseError } from "#common/errors/network";
|
||||
@@ -154,7 +152,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleUpdate({ errors: parsedError });
|
||||
this.dispatchEvents({ update: { errors: parsedError } });
|
||||
this.state = "reviewing";
|
||||
}
|
||||
}
|
||||
@@ -243,7 +241,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
}
|
||||
}
|
||||
|
||||
this.handleUpdate({ errors: parsedError });
|
||||
this.dispatchEvents({ update: { errors: parsedError } });
|
||||
this.state = "reviewing";
|
||||
});
|
||||
}
|
||||
@@ -269,19 +267,22 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
});
|
||||
}
|
||||
|
||||
get buttons(): WizardButton[] {
|
||||
const forReview: WizardButton[] = [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "bindings" },
|
||||
{ kind: "next", label: msg("Create Application"), destination: "here" },
|
||||
];
|
||||
|
||||
const forSubmit: WizardButton[] = [{ kind: "close" }];
|
||||
|
||||
protected get buttons(): WizardButton[] {
|
||||
return match(this.state)
|
||||
.with("submitted", () => forSubmit)
|
||||
.with("submitted", () => {
|
||||
return [
|
||||
{ kind: "close" },
|
||||
{ kind: "finish", destination: "close" },
|
||||
] satisfies WizardButton[];
|
||||
})
|
||||
.with("reviewing", () => {
|
||||
return [
|
||||
{ kind: "cancel" },
|
||||
{ kind: "back", destination: "bindings" },
|
||||
{ kind: "next", label: msg("Create Application"), destination: "here" },
|
||||
] satisfies WizardButton[];
|
||||
})
|
||||
.with("running", () => [])
|
||||
.with("reviewing", () => forReview)
|
||||
.exhaustive();
|
||||
}
|
||||
|
||||
@@ -377,36 +378,53 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
|
||||
const metaLaunchUrl = app.metaLaunchUrl?.trim();
|
||||
|
||||
return html`
|
||||
<div class="ak-wizard-main-content">
|
||||
<ak-wizard-title>${msg("Review the Application and Provider")}</ak-wizard-title>
|
||||
<h2 class="pf-c-title pf-m-xl">${msg("Application")}</h2>
|
||||
<dl class="pf-c-description-list">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">${msg("Name")}</dt>
|
||||
<dt class="pf-c-description-list__description">${app.name}</dt>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">${msg("Group")}</dt>
|
||||
<dt class="pf-c-description-list__description">${app.group || msg("-")}</dt>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">${msg("Policy engine mode")}</dt>
|
||||
<dt class="pf-c-description-list__description">
|
||||
${app.policyEngineMode?.toUpperCase()}
|
||||
</dt>
|
||||
</div>
|
||||
${metaLaunchUrl
|
||||
? html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">${msg("Launch URL")}</dt>
|
||||
<dt class="pf-c-description-list__description">${metaLaunchUrl}</dt>
|
||||
</div>`
|
||||
: nothing}
|
||||
</dl>
|
||||
${renderer
|
||||
? html`<h2 class="pf-c-title pf-m-xl pf-u-pt-xl">${msg("Provider")}</h2>
|
||||
${renderer(provider)}`
|
||||
: nothing}
|
||||
return html`<h2 class="pf-c-wizard__main-title">
|
||||
${msg("Review the Application and Provider")}
|
||||
</h2>
|
||||
<fieldset>
|
||||
<legend>${msg("Application Details")}</legend>
|
||||
<dl class="pf-c-description-list">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">${msg("Application Name")}</dt>
|
||||
<dt class="pf-c-description-list__description">${app.name}</dt>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">${msg("Group")}</dt>
|
||||
<dt class="pf-c-description-list__description">
|
||||
${app.group || msg("-")}
|
||||
</dt>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
${msg("Policy engine mode")}
|
||||
</dt>
|
||||
<dt class="pf-c-description-list__description">
|
||||
${app.policyEngineMode?.toUpperCase()}
|
||||
</dt>
|
||||
</div>
|
||||
${
|
||||
metaLaunchUrl
|
||||
? html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
${msg("Launch URL")}
|
||||
</dt>
|
||||
<dt class="pf-c-description-list__description">
|
||||
${metaLaunchUrl}
|
||||
</dt>
|
||||
</div>`
|
||||
: nothing
|
||||
}
|
||||
</dl>
|
||||
</fieldset>
|
||||
|
||||
${
|
||||
renderer
|
||||
? html`<fieldset>
|
||||
<legend>${msg("Provider Details")}</legend>
|
||||
${renderer(provider)}
|
||||
</fieldset>`
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { serializeForm } from "#elements/forms/serialization";
|
||||
import { ApplicationWizardStyles } from "#admin/applications/wizard/ApplicationWizardFormStepStyles.styles";
|
||||
import {
|
||||
ApplicationTransactionValidationError,
|
||||
type ApplicationWizardState,
|
||||
type ApplicationWizardContext,
|
||||
ApplicationWizardStateError,
|
||||
type OneOfProvider,
|
||||
} from "#admin/applications/wizard/steps/providers/shared";
|
||||
@@ -30,7 +30,7 @@ export abstract class ApplicationWizardProviderForm<
|
||||
public abstract label: string;
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public wizard!: ApplicationWizardState<P, E>;
|
||||
public wizard!: ApplicationWizardContext<P, E>;
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public errors: E = {} as E;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
|
||||
import { WithBrandConfig } from "#elements/mixins/branding";
|
||||
|
||||
import { ApplicationWizardProviderForm } from "#admin/applications/wizard/steps/providers/ApplicationWizardProviderForm";
|
||||
@@ -22,12 +20,10 @@ export class ApplicationWizardLdapProviderForm extends WithBrandConfig(
|
||||
label = msg("Configure LDAP Provider");
|
||||
|
||||
renderForm(provider: LDAPProvider, errors: WizardValidationRecord) {
|
||||
return html`
|
||||
<ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
${renderForm({ provider, errors, brand: this.brand })}
|
||||
</form>
|
||||
`;
|
||||
</form>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { ApplicationWizardProviderForm } from "#admin/applications/wizard/steps/providers/ApplicationWizardProviderForm";
|
||||
@@ -44,7 +42,7 @@ export class ApplicationWizardOauth2ProviderForm extends ApplicationWizardProvid
|
||||
const showLogoutMethodCallback = (show: boolean) => {
|
||||
this.showLogoutMethod = show;
|
||||
};
|
||||
return html` <ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
${renderForm({
|
||||
provider,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
|
||||
import { WizardUpdateEvent } from "#components/ak-wizard/events";
|
||||
|
||||
import { ApplicationWizardProviderForm } from "#admin/applications/wizard/steps/providers/ApplicationWizardProviderForm";
|
||||
@@ -39,7 +37,7 @@ export class ApplicationWizardProxyProviderForm extends ApplicationWizardProvide
|
||||
this.showHttpBasic = el.checked;
|
||||
};
|
||||
|
||||
return html` <ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
${renderForm({
|
||||
provider,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
import "#admin/common/ak-crypto-certificate-search";
|
||||
import "#admin/common/ak-flow-search/ak-flow-search";
|
||||
import "#components/ak-text-input";
|
||||
@@ -23,8 +22,7 @@ export class ApplicationWizardRACProviderForm extends ApplicationWizardProviderF
|
||||
label = msg("Configure Remote Access Provider");
|
||||
|
||||
renderForm(provider: RACProvider) {
|
||||
return html`
|
||||
<ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
<ak-text-input
|
||||
name="name"
|
||||
@@ -36,7 +34,7 @@ export class ApplicationWizardRACProviderForm extends ApplicationWizardProviderF
|
||||
|
||||
<ak-form-element-horizontal
|
||||
name="authorizationFlow"
|
||||
label=${msg("Authorization flow")}
|
||||
label=${msg("Authorization Flow")}
|
||||
required
|
||||
>
|
||||
<ak-flow-search
|
||||
@@ -75,8 +73,7 @@ export class ApplicationWizardRACProviderForm extends ApplicationWizardProviderF
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>
|
||||
`;
|
||||
</form>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
|
||||
import { WithBrandConfig } from "#elements/mixins/branding";
|
||||
|
||||
import { ApplicationWizardProviderForm } from "#admin/applications/wizard/steps/providers/ApplicationWizardProviderForm";
|
||||
@@ -19,7 +17,7 @@ export class ApplicationWizardRadiusProviderForm extends WithBrandConfig(
|
||||
label = msg("Configure Radius Provider");
|
||||
|
||||
renderForm(provider: RadiusProvider, errors: WizardValidationRecord = {}) {
|
||||
return html` <ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
${renderForm({ provider, errors, brand: this.brand })}
|
||||
</form>`;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
|
||||
import { createFileMap } from "#elements/utils/inputs";
|
||||
|
||||
import { ApplicationWizardProviderForm } from "#admin/applications/wizard/steps/providers/ApplicationWizardProviderForm";
|
||||
@@ -30,12 +28,10 @@ export class ApplicationWizardProviderSamlMetadataForm extends ApplicationWizard
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
return html`
|
||||
<ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
${renderForm(this.wizard.provider)}
|
||||
</form>
|
||||
`;
|
||||
</form>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
import "#elements/forms/FormGroup";
|
||||
|
||||
import { ApplicationWizardProviderForm } from "#admin/applications/wizard/steps/providers/ApplicationWizardProviderForm";
|
||||
@@ -80,7 +79,7 @@ export class ApplicationWizardProviderSamlForm extends ApplicationWizardProvider
|
||||
this.logoutMethod = target.value;
|
||||
};
|
||||
|
||||
return html` <ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
${renderForm({
|
||||
provider: this.wizard.provider,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
import "#elements/forms/FormGroup";
|
||||
|
||||
import { ApplicationWizardProviderForm } from "#admin/applications/wizard/steps/providers/ApplicationWizardProviderForm";
|
||||
@@ -18,7 +17,7 @@ export class ApplicationWizardSCIMProvider extends ApplicationWizardProviderForm
|
||||
propertyMappings?: PaginatedSCIMMappingList;
|
||||
|
||||
render() {
|
||||
return html`<ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
${renderForm({
|
||||
update: this.requestUpdate.bind(this),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "#admin/applications/wizard/ak-wizard-title";
|
||||
import "#elements/forms/FormGroup";
|
||||
|
||||
import { ApplicationWizardProviderForm } from "./ApplicationWizardProviderForm.js";
|
||||
@@ -30,7 +29,7 @@ export class ApplicationWizardProviderWSFedForm extends ApplicationWizardProvide
|
||||
this.signingKeyType = target.selectedKeypair?.keyType ?? KeyTypeEnum.Rsa;
|
||||
};
|
||||
|
||||
return html` <ak-wizard-title>${this.label}</ak-wizard-title>
|
||||
return html`<h3 class="pf-c-wizard__main-title">${this.label}</h3>
|
||||
<form id="providerform" class="pf-c-form pf-m-horizontal" slot="form">
|
||||
${renderForm({
|
||||
provider: this.wizard.provider as WSFederationProvider,
|
||||
|
||||
@@ -67,7 +67,7 @@ export type ApplicationWizardStateError = ValidationError | ApplicationTransacti
|
||||
// configured bindings" page in the wizard. The PolicyBinding is converted into a
|
||||
// PolicyBindingRequest during the submission phase.
|
||||
|
||||
export interface ApplicationWizardState<
|
||||
export interface ApplicationWizardContext<
|
||||
P extends OneOfProvider = OneOfProvider,
|
||||
E = ApplicationTransactionValidationError,
|
||||
> {
|
||||
@@ -80,7 +80,7 @@ export interface ApplicationWizardState<
|
||||
errors: E;
|
||||
}
|
||||
|
||||
export interface ApplicationWizardStateUpdate {
|
||||
export interface ApplicationWizardContextUpdate {
|
||||
app?: Partial<ApplicationRequest>;
|
||||
providerModel?: string;
|
||||
provider?: OneOfProvider;
|
||||
|
||||
@@ -31,6 +31,9 @@ export enum BlueprintSource {
|
||||
|
||||
@customElement("ak-blueprint-form")
|
||||
export class BlueprintForm extends ModelForm<BlueprintInstance, string> {
|
||||
public static override verboseName = msg("Blueprint");
|
||||
public static override verboseNamePlural = msg("Blueprints");
|
||||
|
||||
@state()
|
||||
protected source: BlueprintSource = BlueprintSource.File;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import "#elements/forms/HorizontalFormElement";
|
||||
import "#components/ak-toggle-group";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { PFSize } from "#common/enums";
|
||||
|
||||
import { Form } from "#elements/forms/Form";
|
||||
import { PreventFormSubmit } from "#elements/forms/helpers";
|
||||
@@ -34,6 +35,14 @@ import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList
|
||||
export class BlueprintImportForm extends Form<ManagedBlueprintsImportCreateRequest> {
|
||||
static styles: CSSResult[] = [...super.styles, PFDescriptionList, PFBanner];
|
||||
|
||||
public static override verboseName = msg("Flow Blueprint");
|
||||
public static override verboseNamePlural = msg("Flow Blueprints");
|
||||
public static override createLabel = msg("Import");
|
||||
public static override submitVerb = msg("Import");
|
||||
public static override submittingVerb = msg("Importing");
|
||||
|
||||
public override size = PFSize.Medium;
|
||||
|
||||
@state()
|
||||
protected result: BlueprintImportResult | null = null;
|
||||
|
||||
@@ -98,6 +107,11 @@ export class BlueprintImportForm extends Form<ManagedBlueprintsImportCreateReque
|
||||
</ak-toggle-group>
|
||||
${this.source === BlueprintSource.Upload
|
||||
? html`
|
||||
${this.findSlotted("banner-warning")
|
||||
? html`<div class="pf-c-banner pf-m-warning" slot="above-form">
|
||||
<slot name="banner-warning"></slot>
|
||||
</div>`
|
||||
: null}
|
||||
<ak-form-element-horizontal name="blueprint">
|
||||
${AKLabel(
|
||||
{
|
||||
@@ -123,24 +137,20 @@ export class BlueprintImportForm extends Form<ManagedBlueprintsImportCreateReque
|
||||
".yaml files, which can be found in the Example Flows documentation",
|
||||
)}
|
||||
</p>
|
||||
${this.hasSlotted("read-more-link")
|
||||
${this.findSlotted("read-more-link")
|
||||
? html`<p class="pf-c-form__helper-text">
|
||||
${msg("Read more about")}
|
||||
<slot name="read-more-link"></slot>
|
||||
</p>`
|
||||
: nothing}
|
||||
: null}
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
${this.hasSlotted("banner-warning")
|
||||
? html`<div class="pf-c-banner pf-m-warning" slot="above-form">
|
||||
<slot name="banner-warning"></slot>
|
||||
</div>`
|
||||
: nothing}
|
||||
`
|
||||
: nothing}
|
||||
: null}
|
||||
${this.source === BlueprintSource.File
|
||||
? html`<ak-form-element-horizontal label=${msg("Path")} name="path">
|
||||
<ak-search-select
|
||||
placeholder=${msg("Select a blueprint...")}
|
||||
.fetchObjects=${async (query?: string): Promise<BlueprintFile[]> => {
|
||||
const items = await new ManagedApi(
|
||||
DEFAULT_CONFIG,
|
||||
|
||||
@@ -14,10 +14,14 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { EVENT_REFRESH } from "#common/constants";
|
||||
import { docLink } from "#common/global";
|
||||
|
||||
import { IconEditButton, modalInvoker, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { IconPermissionButton } from "#elements/dialogs/components/IconPermissionButton";
|
||||
import { PaginatedResponse, TableColumn, Timestamp } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { BlueprintForm } from "#admin/blueprints/BlueprintForm";
|
||||
|
||||
import {
|
||||
BlueprintInstance,
|
||||
BlueprintInstanceStatusEnum,
|
||||
@@ -26,8 +30,9 @@ import {
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { CSSResult, html, nothing } from "lit";
|
||||
import { guard } from "lit-html/directives/guard.js";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
|
||||
@@ -58,27 +63,28 @@ export function formatBlueprintDescription(item: BlueprintInstance): string | nu
|
||||
|
||||
@customElement("ak-blueprint-list")
|
||||
export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
|
||||
|
||||
protected override searchEnabled = true;
|
||||
|
||||
public pageTitle = msg("Blueprints");
|
||||
public pageDescription = msg("Automate and template configuration within authentik.");
|
||||
public pageIcon = "pf-icon pf-icon-blueprint";
|
||||
|
||||
expandable = true;
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
public override expandable = true;
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
public override searchPlaceholder = msg("Search for a blueprint by name or path...");
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
public override order = "name";
|
||||
|
||||
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<BlueprintInstance>> {
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<BlueprintInstance>> {
|
||||
return new ManagedApi(DEFAULT_CONFIG).managedBlueprintsList(
|
||||
await this.defaultEndpointConfig(),
|
||||
);
|
||||
}
|
||||
|
||||
protected columns: TableColumn[] = [
|
||||
protected override columns: TableColumn[] = [
|
||||
[msg("Name"), "name"],
|
||||
[msg("Status"), "status"],
|
||||
[msg("Last applied"), "last_applied"],
|
||||
@@ -86,7 +92,7 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Blueprint(s)")}
|
||||
@@ -111,7 +117,7 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
renderExpanded(item: BlueprintInstance): TemplateResult {
|
||||
protected override renderExpanded(item: BlueprintInstance): SlottedTemplateResult {
|
||||
const [appLabel, modelName] = ModelEnum.AuthentikBlueprintsBlueprintinstance.split(".");
|
||||
|
||||
return html`<dl class="pf-c-description-list pf-m-horizontal">
|
||||
@@ -144,7 +150,7 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
</dl>`;
|
||||
}
|
||||
|
||||
row(item: BlueprintInstance): SlottedTemplateResult[] {
|
||||
protected override row(item: BlueprintInstance): SlottedTemplateResult[] {
|
||||
const description = formatBlueprintDescription(item);
|
||||
|
||||
return [
|
||||
@@ -152,30 +158,16 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
${description
|
||||
? html`<small><ak-mdx .content=${description}></ak-mdx></small>`
|
||||
: nothing}`,
|
||||
html`${BlueprintStatus(item)}`,
|
||||
BlueprintStatus(item),
|
||||
Timestamp(item.lastApplied),
|
||||
html`<ak-status-label ?good=${item.enabled}></ak-status-label>`,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Blueprint")}</span>
|
||||
<ak-blueprint-form slot="form" .instancePk=${item.pk}> </ak-blueprint-form>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-plain"
|
||||
aria-label=${msg(str`Edit "${item.name}" blueprint`)}
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-rbac-object-permission-modal
|
||||
label=${item.name}
|
||||
model=${ModelEnum.AuthentikBlueprintsBlueprintinstance}
|
||||
objectPk=${item.pk}
|
||||
>
|
||||
</ak-rbac-object-permission-modal>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(BlueprintForm, item.pk, item.name)}
|
||||
${IconPermissionButton(item.name, {
|
||||
model: ModelEnum.AuthentikBlueprintsBlueprintinstance,
|
||||
objectPk: item.pk,
|
||||
})}
|
||||
|
||||
<ak-action-button
|
||||
class="pf-m-plain"
|
||||
label=${msg(str`Apply "${item.name}" blueprint`)}
|
||||
@@ -202,36 +194,36 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
];
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("Create Blueprint Instance")}</span>
|
||||
<ak-blueprint-form slot="form"> </ak-blueprint-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||
</ak-forms-modal>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Import")}</span>
|
||||
<span slot="header">${msg("Import Blueprint")}</span>
|
||||
<ak-blueprint-import-form slot="form">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href=${docLink("/customize/blueprints/working_with_blueprints/")}
|
||||
slot="read-more-link"
|
||||
>${msg("Flow Examples")}</a
|
||||
>
|
||||
<span slot="banner-warning">
|
||||
${msg(
|
||||
"Warning: Blueprint files may contain objects such as users, policies and expression.",
|
||||
)}<br />${msg(
|
||||
"You should only import files from trusted sources and review blueprints before importing them.",
|
||||
)}
|
||||
</span>
|
||||
</ak-blueprint-import-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">${msg("Import")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return guard([], () => {
|
||||
return [
|
||||
ModalInvokerButton(BlueprintForm),
|
||||
html`<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="button"
|
||||
${modalInvoker(() => {
|
||||
return html`<ak-blueprint-import-form>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href=${docLink("/customize/blueprints/working_with_blueprints/")}
|
||||
slot="read-more-link"
|
||||
>${msg("Flow Examples")}</a
|
||||
>
|
||||
<span slot="banner-warning">
|
||||
${msg(
|
||||
"Warning: Blueprint files may contain objects such as users, policies and expression.",
|
||||
)}<br />${msg(
|
||||
"You should only import files from trusted sources and review blueprints before importing them.",
|
||||
)}
|
||||
</span>
|
||||
</ak-blueprint-import-form>`;
|
||||
})}
|
||||
>
|
||||
${msg("Import")}
|
||||
</button>`,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-brand-form")
|
||||
export class BrandForm extends ModelForm<Brand, string> {
|
||||
public static override verboseName = msg("Brand");
|
||||
public static override verboseNamePlural = msg("Brands");
|
||||
|
||||
loadInstance(pk: string): Promise<Brand> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreBrandsRetrieve({
|
||||
brandUuid: pk,
|
||||
@@ -192,7 +195,7 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
<ak-form-group label="${msg("Default flows")} ">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication flow")}
|
||||
label=${msg("Authentication Flow")}
|
||||
name="flowAuthentication"
|
||||
>
|
||||
<ak-flow-search
|
||||
@@ -207,7 +210,7 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Invalidation flow")}
|
||||
label=${msg("Invalidation Flow")}
|
||||
name="flowInvalidation"
|
||||
>
|
||||
<ak-flow-search
|
||||
|
||||
@@ -8,14 +8,17 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEditButton, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { BrandForm } from "#admin/brands/BrandForm";
|
||||
|
||||
import { Brand, CoreApi, ModelEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-brand-list")
|
||||
@@ -47,8 +50,9 @@ export class BrandListPage extends TablePage<Brand> {
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Brand(s)")}
|
||||
.objects=${this.selectedElements}
|
||||
@@ -72,22 +76,13 @@ export class BrandListPage extends TablePage<Brand> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: Brand): SlottedTemplateResult[] {
|
||||
protected override row(item: Brand): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`${item.domain}`,
|
||||
html`${item.brandingTitle}`,
|
||||
item.domain,
|
||||
item.brandingTitle || msg("-"),
|
||||
html`<ak-status-label ?good=${item._default}></ak-status-label>`,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Brand")}</span>
|
||||
<ak-brand-form slot="form" .instancePk=${item.brandUuid}> </ak-brand-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(BrandForm, item.brandUuid, item.brandingTitle)}
|
||||
|
||||
<ak-rbac-object-permission-modal
|
||||
model=${ModelEnum.AuthentikBrandsBrand}
|
||||
@@ -98,15 +93,8 @@ export class BrandListPage extends TablePage<Brand> {
|
||||
];
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create Brand")}</span>
|
||||
<span slot="header">${msg("New Brand")}</span>
|
||||
<ak-brand-form slot="form"> </ak-brand-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("New Brand")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(BrandForm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "#components/ak-text-input";
|
||||
import "#components/ak-number-input";
|
||||
import "#elements/forms/Radio";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
|
||||
@@ -17,7 +19,12 @@ import { html, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-crypto-certificate-generate-form")
|
||||
export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
|
||||
export class CryptoCertificateGenerateForm extends Form<CertificateGenerationRequest> {
|
||||
public static override verboseName = msg("Certificate-Key Pair");
|
||||
public static override verboseNamePlural = msg("Certificate-Key Pairs");
|
||||
public static override createLabel = msg("Generate");
|
||||
public static override submitVerb = msg("Generate");
|
||||
|
||||
getSuccessMessage(): string {
|
||||
return msg("Successfully generated certificate-key pair.");
|
||||
}
|
||||
@@ -29,22 +36,31 @@ export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html`<ak-form-element-horizontal
|
||||
return html`<ak-text-input
|
||||
label=${msg("Common Name")}
|
||||
name="commonName"
|
||||
required
|
||||
>
|
||||
<input type="text" class="pf-c-form-control" required />
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Subject-alt name")} name="subjectAltName">
|
||||
<input class="pf-c-form-control" type="text" />
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Optional, comma-separated SubjectAlt Names.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Validity days")} name="validityDays" required>
|
||||
<input class="pf-c-form-control" type="number" value="365" />
|
||||
</ak-form-element-horizontal>
|
||||
placeholder=${msg("Type a name for this certificate...")}
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
label=${msg("Subject-alt name")}
|
||||
name="subjectAltName"
|
||||
autocomplete="off"
|
||||
input-hint="code"
|
||||
help=${msg("Optional, comma-separated SubjectAlt Names.")}
|
||||
placeholder=${msg("e.g. mydomain.com, *.mydomain.com, mydomain.local")}
|
||||
></ak-text-input>
|
||||
|
||||
<ak-number-input
|
||||
label=${msg("Validity days")}
|
||||
name="validityDays"
|
||||
required
|
||||
value="365"
|
||||
></ak-number-input>
|
||||
|
||||
<ak-form-element-horizontal label=${msg("Private key Algorithm")} required name="alg">
|
||||
<ak-radio
|
||||
.options=${[
|
||||
@@ -77,6 +93,6 @@ export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-crypto-certificate-generate-form": CertificateKeyPairForm;
|
||||
"ak-crypto-certificate-generate-form": CryptoCertificateGenerateForm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "#components/ak-secret-textarea-input";
|
||||
import "#components/ak-text-input";
|
||||
import "#elements/CodeMirror";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
|
||||
@@ -14,7 +15,13 @@ import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@customElement("ak-crypto-certificate-form")
|
||||
export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string> {
|
||||
export class CryptoCertificateForm extends ModelForm<CertificateKeyPair, string> {
|
||||
public static override verboseName = msg("Certificate-Key Pair");
|
||||
public static override verboseNamePlural = msg("Certificate-Key Pairs");
|
||||
public static override createLabel = msg("Import Existing");
|
||||
public static override submitVerb = msg("Import");
|
||||
public static override submittingVerb = msg("Importing");
|
||||
|
||||
loadInstance(pk: string): Promise<CertificateKeyPair> {
|
||||
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsRetrieve({
|
||||
kpUuid: pk,
|
||||
@@ -40,14 +47,16 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal label=${msg("Name")} name="name" required>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
return html`<ak-text-input
|
||||
label=${msg("Certificate Name")}
|
||||
name="name"
|
||||
required
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
placeholder=${msg("Type a name for this certificate-key pair...")}
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
></ak-text-input>
|
||||
<ak-secret-textarea-input
|
||||
label=${msg("Certificate")}
|
||||
name="certificateData"
|
||||
@@ -59,6 +68,7 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
|
||||
></ak-secret-textarea-input>
|
||||
<ak-secret-textarea-input
|
||||
label=${msg("Private Key")}
|
||||
placeholder="-----BEGIN PRIVATE KEY-----"
|
||||
name="keyData"
|
||||
input-hint="code"
|
||||
?revealed=${!this.instance}
|
||||
@@ -71,6 +81,6 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-crypto-certificate-form": CertificateKeyPairForm;
|
||||
"ak-crypto-certificate-form": CryptoCertificateForm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,36 +9,41 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PFColor } from "#elements/Label";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { CryptoCertificateGenerateForm } from "#admin/crypto/CertificateGenerateForm";
|
||||
import { CryptoCertificateForm } from "#admin/crypto/CertificateKeyPairForm";
|
||||
|
||||
import { CertificateKeyPair, CryptoApi, ModelEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { CSSResult, html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
|
||||
@customElement("ak-crypto-certificate-list")
|
||||
export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
||||
expandable = true;
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
|
||||
|
||||
public override expandable = true;
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
public override searchPlaceholder = msg("Search for a certificate or key name...");
|
||||
|
||||
protected override searchEnabled = true;
|
||||
|
||||
public pageTitle = msg("Certificate-Key Pairs");
|
||||
public pageDescription = msg(
|
||||
"Import certificates of external providers or create certificates to sign requests with.",
|
||||
);
|
||||
public pageIcon = "pf-icon pf-icon-key";
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
|
||||
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
|
||||
public override order = "name";
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<CertificateKeyPair>> {
|
||||
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({
|
||||
@@ -53,7 +58,7 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
const count = this.selectedElements.length;
|
||||
return html`<ak-forms-delete-bulk
|
||||
@@ -82,7 +87,7 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: CertificateKeyPair): SlottedTemplateResult[] {
|
||||
protected override row(item: CertificateKeyPair): SlottedTemplateResult[] {
|
||||
let managedSubText = msg("Managed by authentik");
|
||||
if (item.managed && item.managed.startsWith("goauthentik.io/crypto/discovered")) {
|
||||
managedSubText = msg("Managed by authentik (Discovered)");
|
||||
@@ -130,7 +135,7 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: CertificateKeyPair): TemplateResult {
|
||||
protected override renderExpanded(item: CertificateKeyPair): SlottedTemplateResult {
|
||||
return html`<dl class="pf-c-description-list pf-m-horizontal">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
@@ -188,24 +193,13 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
||||
</dl>`;
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Import")}</span>
|
||||
<span slot="header">${msg("Import Existing Certificate-Key Pair")}</span>
|
||||
<ak-crypto-certificate-form slot="form"> </ak-crypto-certificate-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Import")}</button>
|
||||
</ak-forms-modal>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Generate")}</span>
|
||||
<span slot="header">${msg("Generate New Certificate-Key Pair")}</span>
|
||||
<ak-crypto-certificate-generate-form slot="form">
|
||||
</ak-crypto-certificate-generate-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${msg("Generate")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return [
|
||||
ModalInvokerButton(CryptoCertificateForm),
|
||||
ModalInvokerButton(CryptoCertificateGenerateForm, null, {
|
||||
kind: "secondary",
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import "#components/ak-text-input";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { PFSize } from "#common/enums";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
import { WithBrandConfig } from "#elements/mixins/branding";
|
||||
@@ -20,35 +21,42 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
*/
|
||||
@customElement("ak-endpoints-device-access-groups-form")
|
||||
export class DeviceAccessGroupForm extends WithBrandConfig(ModelForm<DeviceAccessGroup, string>) {
|
||||
loadInstance(pk: string): Promise<DeviceAccessGroup> {
|
||||
public static override verboseName = msg("Device Access Group");
|
||||
public static override verboseNamePlural = msg("Device Access Groups");
|
||||
|
||||
public override size = PFSize.Small;
|
||||
|
||||
protected override loadInstance(pk: string): Promise<DeviceAccessGroup> {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsDeviceAccessGroupsRetrieve({
|
||||
pbmUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
public override getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated group.")
|
||||
: msg("Successfully created group.");
|
||||
}
|
||||
|
||||
async send(data: DeviceAccessGroup): Promise<DeviceAccessGroup> {
|
||||
protected override async send(data: DeviceAccessGroup): Promise<DeviceAccessGroup> {
|
||||
if (this.instance) {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsDeviceAccessGroupsPartialUpdate({
|
||||
pbmUuid: this.instance.pbmUuid,
|
||||
patchedDeviceAccessGroupRequest: data,
|
||||
});
|
||||
}
|
||||
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsDeviceAccessGroupsCreate({
|
||||
deviceAccessGroupRequest: data as unknown as DeviceAccessGroupRequest,
|
||||
});
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
protected override renderForm() {
|
||||
return html`<ak-text-input
|
||||
name="name"
|
||||
placeholder=${msg("Group name...")}
|
||||
label=${msg("Group name")}
|
||||
autocomplete="off"
|
||||
placeholder=${msg("Type a group name...")}
|
||||
label=${msg("Group Name")}
|
||||
value=${ifDefined(this.instance?.name)}
|
||||
required
|
||||
></ak-text-input>`;
|
||||
|
||||
@@ -6,10 +6,13 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEditButton, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { DeviceAccessGroupForm } from "#admin/endpoints/DeviceAccessGroupForm";
|
||||
|
||||
import { DeviceAccessGroup, EndpointsApi } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@@ -18,59 +21,44 @@ import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-endpoints-device-access-groups-list")
|
||||
export class DeviceAccessGroupsListPage extends TablePage<DeviceAccessGroup> {
|
||||
public pageIcon = "pf-icon pf-icon-server-group ";
|
||||
public pageTitle = msg("Device access groups");
|
||||
public pageDescription = msg("Create groups of devices to manage access.");
|
||||
|
||||
protected searchEnabled: boolean = true;
|
||||
protected columns: TableColumn[] = [
|
||||
[msg("Name"), "name"],
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
checkbox = true;
|
||||
expandable = true;
|
||||
public override pageIcon = "pf-icon pf-icon-server-group ";
|
||||
public override pageTitle = msg("Device access groups");
|
||||
public override pageDescription = msg("Create groups of devices to manage access.");
|
||||
public override searchPlaceholder = msg("Search device groups by name...");
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<DeviceAccessGroup>> {
|
||||
public override checkbox = true;
|
||||
public override expandable = true;
|
||||
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<DeviceAccessGroup>> {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsDeviceAccessGroupsList(
|
||||
await this.defaultEndpointConfig(),
|
||||
);
|
||||
}
|
||||
|
||||
row(item: DeviceAccessGroup): SlottedTemplateResult[] {
|
||||
protected override row(item: DeviceAccessGroup): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Group")}</span>
|
||||
<ak-endpoints-device-access-groups-form slot="form" .instancePk=${item.pbmUuid}>
|
||||
</ak-endpoints-device-access-groups-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
// ---
|
||||
item.name,
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(DeviceAccessGroupForm, item.pbmUuid)}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: DeviceAccessGroup) {
|
||||
protected override renderExpanded(item: DeviceAccessGroup) {
|
||||
return html`<div class="pf-c-content">
|
||||
<ak-bound-policies-list .target=${item.pbmUuid}></ak-bound-policies-list>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderObjectCreate() {
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("Create Device Group")}</span>
|
||||
<ak-endpoints-device-access-groups-form
|
||||
slot="form"
|
||||
></ak-endpoints-device-access-groups-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||
</ak-forms-modal>`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(DeviceAccessGroupForm);
|
||||
}
|
||||
|
||||
renderToolbarSelected() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
EndpointsDeviceAccessGroupsListRequest,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
|
||||
@@ -85,6 +86,7 @@ export class EndpointsDeviceAccessGroupSearch extends CustomListenerElement(AKEl
|
||||
render() {
|
||||
return html`
|
||||
<ak-search-select
|
||||
placeholder=${msg("Select a device access group...")}
|
||||
.fetchObjects=${fetchObjects}
|
||||
.renderElement=${renderElement}
|
||||
.value=${renderValue}
|
||||
|
||||
@@ -8,92 +8,34 @@ import "#elements/wizard/Wizard";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
import { TypeCreateWizardPageLayouts } from "#elements/wizard/TypeCreateWizardPage";
|
||||
import { Wizard } from "#elements/wizard/Wizard";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { CreateWizard } from "#elements/wizard/CreateWizard";
|
||||
|
||||
import { EndpointsApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, html, TemplateResult } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
|
||||
@customElement("ak-endpoint-connector-wizard")
|
||||
export class EndpointConnectorWizard extends AKElement {
|
||||
static styles: CSSResult[] = [PFButton];
|
||||
export class AKEndpointConnectorWizard extends CreateWizard {
|
||||
#api = new EndpointsApi(DEFAULT_CONFIG);
|
||||
|
||||
@property()
|
||||
createText = msg("Create");
|
||||
public static override verboseName = msg("Endpoint Connector");
|
||||
public static override verboseNamePlural = msg("Endpoint Connectors");
|
||||
|
||||
@property({ attribute: false })
|
||||
connectorTypes: TypeCreate[] = [];
|
||||
protected apiEndpoint = (requestInit?: RequestInit): Promise<TypeCreate[]> => {
|
||||
return this.#api.endpointsConnectorsTypesList(requestInit);
|
||||
};
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
firstUpdated(): void {
|
||||
new EndpointsApi(DEFAULT_CONFIG).endpointsConnectorsTypesList().then((types) => {
|
||||
this.connectorTypes = types;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<ak-wizard
|
||||
.steps=${["initial"]}
|
||||
header=${msg("New connector")}
|
||||
description=${msg("Create a new connector.")}
|
||||
>
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.connectorTypes}
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
const idx = this.wizard.steps.indexOf("initial") + 1;
|
||||
// Exclude all current steps starting with type-,
|
||||
// this happens when the user selects a type and then goes back
|
||||
this.wizard.steps = this.wizard.steps.filter(
|
||||
(step) => !step.startsWith("type-"),
|
||||
);
|
||||
this.wizard.steps.splice(
|
||||
idx,
|
||||
0,
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
);
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
<div slot="above-form">
|
||||
<p>
|
||||
${msg(
|
||||
"Connectors are required to create devices. Depending on connector type, agents either directly talk to them or they talk to and external API to create devices.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.connectorTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
slot=${`type-${type.component}-${type.modelName}`}
|
||||
label=${msg(str`Create ${type.name}`)}
|
||||
>
|
||||
${StrictUnsafe(type.component)}
|
||||
</ak-wizard-page-form>
|
||||
`;
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
|
||||
</ak-wizard>
|
||||
`;
|
||||
protected override renderInitialPageContent(): SlottedTemplateResult {
|
||||
return msg(
|
||||
"Connectors are required to create devices. Depending on connector type, agents either directly talk to them or they talk to and external API to create devices.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-endpoint-connector-wizard": EndpointConnectorWizard;
|
||||
"ak-endpoint-connector-wizard": AKEndpointConnectorWizard;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,70 +7,58 @@ import "#elements/forms/ModalForm";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { CustomFormElementTagName } from "#elements/forms/unsafe";
|
||||
import { IconEditButtonByTagName, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
|
||||
import { AKEndpointConnectorWizard } from "#admin/endpoints/connectors/ConnectorWizard";
|
||||
|
||||
import { Connector, EndpointsApi } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-endpoints-connectors-list")
|
||||
export class ConnectorsListPage extends TablePage<Connector> {
|
||||
public pageIcon = "pf-icon pf-icon-data-source";
|
||||
public pageTitle = msg("Connectors");
|
||||
public pageDescription = msg(
|
||||
public override searchPlaceholder = msg("Search connectors by name or type...");
|
||||
public override pageIcon = "pf-icon pf-icon-data-source";
|
||||
public override pageTitle = msg("Connectors");
|
||||
public override pageDescription = msg(
|
||||
"Configure how devices connect with authentik and ingest external device data.",
|
||||
);
|
||||
|
||||
protected searchEnabled: boolean = true;
|
||||
protected columns: TableColumn[] = [
|
||||
protected override searchEnabled: boolean = true;
|
||||
protected override columns: TableColumn[] = [
|
||||
[msg("Name"), "name"],
|
||||
[msg("Type")],
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
checkbox = true;
|
||||
public override checkbox = true;
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<Connector>> {
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<Connector>> {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsConnectorsList(
|
||||
await this.defaultEndpointConfig(),
|
||||
);
|
||||
}
|
||||
|
||||
row(item: Connector): SlottedTemplateResult[] {
|
||||
protected override row(item: Connector): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`<a href="#/endpoints/connectors/${item.connectorUuid}">${item.name}</a>`,
|
||||
html`${item.verboseName}`,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
${StrictUnsafe<CustomFormElementTagName>(item.component, {
|
||||
slot: "form",
|
||||
instancePk: item.connectorUuid,
|
||||
submitLabel: msg("Save Changes"),
|
||||
headline: msg(str`Update ${item.verboseName}`, {
|
||||
id: "form.headline.update",
|
||||
}),
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
item.verboseName,
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButtonByTagName(item.component, item.connectorUuid, item.verboseName)}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderObjectCreate() {
|
||||
return html`<ak-endpoint-connector-wizard></ak-endpoint-connector-wizard> `;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(AKEndpointConnectorWizard);
|
||||
}
|
||||
|
||||
renderToolbarSelected() {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Connector(s)")}
|
||||
|
||||
@@ -89,11 +89,11 @@ export class AgentConnectorForm extends WithBrandConfig(ModelForm<AgentConnector
|
||||
<ak-form-group label="${msg("Authentication settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authorization flow")}
|
||||
label=${msg("Authorization Flow")}
|
||||
name="authorizationFlow"
|
||||
>
|
||||
<ak-flow-search
|
||||
label=${msg("Authorization flow")}
|
||||
label=${msg("Authorization Flow")}
|
||||
flowType=${FlowDesignationEnum.Authorization}
|
||||
.currentFlow=${this.instance?.authorizationFlow}
|
||||
></ak-flow-search>
|
||||
|
||||
@@ -29,13 +29,18 @@ const EXPIRATION_DURATION = 30 * 60 * 1000; // 30 minutes
|
||||
*/
|
||||
@customElement("ak-endpoints-agent-enrollment-token-form")
|
||||
export class EnrollmentTokenForm extends WithBrandConfig(ModelForm<EnrollmentToken, string>) {
|
||||
#api = new EndpointsApi(DEFAULT_CONFIG);
|
||||
|
||||
public static override verboseName = msg("Enrollment Token");
|
||||
public static override verboseNamePlural = msg("Enrollment Tokens");
|
||||
|
||||
protected expirationMinimumDate = new Date();
|
||||
|
||||
@state()
|
||||
protected expiresAt: Date | null = new Date(Date.now() + EXPIRATION_DURATION);
|
||||
|
||||
@property({ type: String, attribute: "connector-id" })
|
||||
public connectorID?: string;
|
||||
public connectorID: string | null = null;
|
||||
|
||||
public override reset(): void {
|
||||
super.reset();
|
||||
@@ -57,25 +62,25 @@ export class EnrollmentTokenForm extends WithBrandConfig(ModelForm<EnrollmentTok
|
||||
return token;
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
public override getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated token.")
|
||||
: msg("Successfully created token.");
|
||||
}
|
||||
|
||||
async send(data: EnrollmentToken): Promise<EnrollmentToken> {
|
||||
protected override async send(data: EnrollmentToken): Promise<EnrollmentToken> {
|
||||
if (!this.instance) {
|
||||
data.connector = this.connectorID || "";
|
||||
} else {
|
||||
data.connector = this.instance.connector;
|
||||
}
|
||||
if (this.instance) {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsAgentsEnrollmentTokensPartialUpdate({
|
||||
return this.#api.endpointsAgentsEnrollmentTokensPartialUpdate({
|
||||
tokenUuid: this.instance.tokenUuid,
|
||||
patchedEnrollmentTokenRequest: data,
|
||||
});
|
||||
}
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsAgentsEnrollmentTokensCreate({
|
||||
return this.#api.endpointsAgentsEnrollmentTokensCreate({
|
||||
enrollmentTokenRequest: data as unknown as EnrollmentTokenRequest,
|
||||
});
|
||||
}
|
||||
@@ -102,7 +107,7 @@ export class EnrollmentTokenForm extends WithBrandConfig(ModelForm<EnrollmentTok
|
||||
|
||||
//#region Rendering
|
||||
|
||||
renderForm() {
|
||||
protected override renderForm() {
|
||||
return html`<ak-text-input
|
||||
name="name"
|
||||
placeholder=${msg("Type a name for the token...")}
|
||||
@@ -148,7 +153,7 @@ export class EnrollmentTokenForm extends WithBrandConfig(ModelForm<EnrollmentTok
|
||||
?disabled=${!this.expiresAt}
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
</ak-form-element-horizontal> `;
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import "#admin/rbac/ObjectPermissionModal";
|
||||
import "#admin/endpoints/connectors/agent/EnrollmentTokenForm";
|
||||
import "#admin/endpoints/connectors/agent/ak-enrollment-token-copy-button";
|
||||
import "#elements/buttons/SpinnerButton/index";
|
||||
import "#elements/forms/DeleteBulkForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
@@ -9,9 +8,14 @@ import "#components/ak-status-label";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEnrollmentTokenCopyButton } from "#elements/buttons/IconEnrollmentTokenCopyButton";
|
||||
import { IconEditButton, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { IconPermissionButton } from "#elements/dialogs/components/IconPermissionButton";
|
||||
import { PaginatedResponse, Table, TableColumn, Timestamp } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { EnrollmentTokenForm } from "#admin/endpoints/connectors/agent/EnrollmentTokenForm";
|
||||
|
||||
import { AgentConnector, EndpointsApi, EnrollmentToken, ModelEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@@ -20,19 +24,23 @@ import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-endpoints-agent-enrollment-token-list")
|
||||
export class EnrollmentTokenListPage extends Table<EnrollmentToken> {
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
#api = new EndpointsApi(DEFAULT_CONFIG);
|
||||
|
||||
protected override searchEnabled = true;
|
||||
protected emptyStateMessage = msg("No enrollment tokens found for this connector.");
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
|
||||
public override searchPlaceholder = msg("Search for an enrollment token...");
|
||||
|
||||
public override order = "name";
|
||||
|
||||
@property({ attribute: false })
|
||||
connector?: AgentConnector;
|
||||
public connector: AgentConnector | null = null;
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<EnrollmentToken>> {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsAgentsEnrollmentTokensList({
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<EnrollmentToken>> {
|
||||
return this.#api.endpointsAgentsEnrollmentTokensList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
connector: this.connector?.connectorUuid,
|
||||
});
|
||||
@@ -46,8 +54,9 @@ export class EnrollmentTokenListPage extends Table<EnrollmentToken> {
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Enrollment Token(s)")}
|
||||
.objects=${this.selectedElements}
|
||||
@@ -58,12 +67,12 @@ export class EnrollmentTokenListPage extends Table<EnrollmentToken> {
|
||||
];
|
||||
}}
|
||||
.usedBy=${(item: EnrollmentToken) => {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsAgentsEnrollmentTokensUsedByList({
|
||||
return this.#api.endpointsAgentsEnrollmentTokensUsedByList({
|
||||
tokenUuid: item.tokenUuid,
|
||||
});
|
||||
}}
|
||||
.delete=${(item: EnrollmentToken) => {
|
||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsAgentsEnrollmentTokensDestroy({
|
||||
return this.#api.endpointsAgentsEnrollmentTokensDestroy({
|
||||
tokenUuid: item.tokenUuid,
|
||||
});
|
||||
}}
|
||||
@@ -74,54 +83,27 @@ export class EnrollmentTokenListPage extends Table<EnrollmentToken> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: EnrollmentToken): SlottedTemplateResult[] {
|
||||
protected override row(item: EnrollmentToken): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`${item.deviceGroupObj?.name || "-"}`,
|
||||
item.name,
|
||||
item.deviceGroupObj?.name || msg("-"),
|
||||
html`<ak-status-label type="warning" ?good=${item.expiring}></ak-status-label>`,
|
||||
Timestamp(item.expires && item.expiring ? item.expires : null),
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Enrollment Token")}</span>
|
||||
<ak-endpoints-agent-enrollment-token-form
|
||||
slot="form"
|
||||
.instancePk=${item.tokenUuid}
|
||||
>
|
||||
</ak-endpoints-agent-enrollment-token-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-rbac-object-permission-modal
|
||||
model=${ModelEnum.AuthentikEndpointsConnectorsAgentEnrollmenttoken}
|
||||
objectPk=${item.tokenUuid}
|
||||
>
|
||||
</ak-rbac-object-permission-modal>
|
||||
<ak-enrollment-token-copy-button .identifier=${item.tokenUuid}>
|
||||
<pf-tooltip position="top" content=${msg("Copy token")}>
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</ak-enrollment-token-copy-button>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(EnrollmentTokenForm, item.tokenUuid, item.name)}
|
||||
${IconPermissionButton(item.name, {
|
||||
model: ModelEnum.AuthentikEndpointsConnectorsAgentEnrollmenttoken,
|
||||
objectPk: item.tokenUuid,
|
||||
})}
|
||||
${IconEnrollmentTokenCopyButton(item.tokenUuid)}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("Create Enrollment Token")}</span>
|
||||
<ak-endpoints-agent-enrollment-token-form
|
||||
slot="form"
|
||||
.connectorID=${this.connector?.connectorUuid}
|
||||
>
|
||||
</ak-endpoints-agent-enrollment-token-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(EnrollmentTokenForm, {
|
||||
connectorID: this.connector?.connectorUuid,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { writeToClipboard } from "#common/clipboard";
|
||||
|
||||
import TokenCopyButton from "#elements/buttons/TokenCopyButton/ak-token-copy-button";
|
||||
|
||||
import { EndpointsApi } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-enrollment-token-copy-button")
|
||||
export class EnrollmentTokenCopyButton extends TokenCopyButton {
|
||||
public override entityLabel = msg("Enrollment Token");
|
||||
|
||||
public override callAction(): Promise<null> {
|
||||
if (!this.identifier) {
|
||||
throw new TypeError("No `identifier` set for `EnrollmentTokenCopyButton`");
|
||||
}
|
||||
|
||||
// Safari permission hack.
|
||||
const text = new ClipboardItem({
|
||||
"text/plain": new EndpointsApi(DEFAULT_CONFIG)
|
||||
.endpointsAgentsEnrollmentTokensViewKeyRetrieve({
|
||||
tokenUuid: this.identifier,
|
||||
})
|
||||
.then((tokenView) => new Blob([tokenView.key], { type: "text/plain" })),
|
||||
});
|
||||
|
||||
return writeToClipboard(text, this.entityLabel).then(() => null);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-enrollment-token-copy-button": EnrollmentTokenCopyButton;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,8 @@ export class FleetConnectorForm extends ModelForm<FleetConnector, string> {
|
||||
renderForm() {
|
||||
return html`<ak-text-input
|
||||
name="name"
|
||||
placeholder=${msg("Connector name...")}
|
||||
autofocus
|
||||
placeholder=${msg("Type a connector name...")}
|
||||
label=${msg("Connector name")}
|
||||
value=${this.instance?.name ?? ""}
|
||||
required
|
||||
@@ -57,6 +58,7 @@ export class FleetConnectorForm extends ModelForm<FleetConnector, string> {
|
||||
<ak-text-input
|
||||
name="url"
|
||||
label=${msg("Fleet Server URL")}
|
||||
inputmode="url"
|
||||
value=${this.instance?.url ?? ""}
|
||||
required
|
||||
input-hint="code"
|
||||
@@ -64,6 +66,7 @@ export class FleetConnectorForm extends ModelForm<FleetConnector, string> {
|
||||
</ak-text-input>
|
||||
<ak-secret-text-input
|
||||
label=${msg("Fleet API Token")}
|
||||
placeholder=${msg("Provide your Fleet API token...")}
|
||||
name="token"
|
||||
?revealed=${!this.instance}
|
||||
></ak-secret-text-input>
|
||||
|
||||
@@ -43,7 +43,8 @@ export class GoogleChromeConnectorForm extends ModelForm<GoogleChromeConnector,
|
||||
renderForm() {
|
||||
return html`<ak-text-input
|
||||
name="name"
|
||||
placeholder=${msg("Connector name...")}
|
||||
autofocus
|
||||
placeholder=${msg("Type a connector name...")}
|
||||
label=${msg("Connector name")}
|
||||
value=${this.instance?.name ?? ""}
|
||||
required
|
||||
|
||||
@@ -21,13 +21,7 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
|
||||
@customElement("ak-endpoints-device-list")
|
||||
export class DeviceListPage extends TablePage<EndpointDevice> {
|
||||
public pageTitle = msg("Devices");
|
||||
public pageDescription = "";
|
||||
public pageIcon = "fa fa-laptop";
|
||||
|
||||
checkbox = true;
|
||||
|
||||
static styles: CSSResult[] = [
|
||||
public static styles: CSSResult[] = [
|
||||
...super.styles,
|
||||
PFGrid,
|
||||
PFBanner,
|
||||
@@ -37,6 +31,13 @@ export class DeviceListPage extends TablePage<EndpointDevice> {
|
||||
}
|
||||
`,
|
||||
];
|
||||
public override pageTitle = msg("Devices");
|
||||
public override pageDescription = "";
|
||||
public override pageIcon = "fa fa-laptop";
|
||||
|
||||
public override checkbox = true;
|
||||
|
||||
public override searchPlaceholder = msg("Search devices by name, OS, or group...");
|
||||
|
||||
protected searchEnabled: boolean = true;
|
||||
protected columns: TableColumn[] = [
|
||||
@@ -59,7 +60,7 @@ export class DeviceListPage extends TablePage<EndpointDevice> {
|
||||
);
|
||||
}
|
||||
|
||||
protected renderEmpty(inner?: TemplateResult): TemplateResult {
|
||||
protected renderEmpty(inner?: TemplateResult): SlottedTemplateResult {
|
||||
return super.renderEmpty(html`
|
||||
${inner
|
||||
? inner
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "#components/ak-secret-textarea-input";
|
||||
import "#components/ak-text-input";
|
||||
import "#elements/CodeMirror";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
|
||||
@@ -6,16 +7,24 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { EVENT_REFRESH_ENTERPRISE } from "#common/constants";
|
||||
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { ifPresent } from "#elements/utils/attributes";
|
||||
|
||||
import { EnterpriseApi, License } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-enterprise-license-form")
|
||||
export class EnterpriseLicenseForm extends ModelForm<License, string> {
|
||||
public static override verboseName = msg("Enterprise License");
|
||||
public static override verboseNamePlural = msg("Enterprise Licenses");
|
||||
public static override createLabel = msg("Install");
|
||||
public static override submitVerb = msg("Install");
|
||||
|
||||
#api = new EnterpriseApi(DEFAULT_CONFIG);
|
||||
|
||||
@state()
|
||||
protected installID: string | null = null;
|
||||
|
||||
@@ -26,7 +35,7 @@ export class EnterpriseLicenseForm extends ModelForm<License, string> {
|
||||
}
|
||||
|
||||
loadInstance(pk: string): Promise<License> {
|
||||
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseRetrieve({
|
||||
return this.#api.enterpriseLicenseRetrieve({
|
||||
licenseUuid: pk,
|
||||
});
|
||||
}
|
||||
@@ -38,19 +47,17 @@ export class EnterpriseLicenseForm extends ModelForm<License, string> {
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
this.installID = (
|
||||
await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseInstallIdRetrieve()
|
||||
).installId;
|
||||
this.installID = (await this.#api.enterpriseLicenseInstallIdRetrieve()).installId;
|
||||
}
|
||||
|
||||
async send(data: License): Promise<License> {
|
||||
return (
|
||||
this.instance
|
||||
? new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicensePartialUpdate({
|
||||
? this.#api.enterpriseLicensePartialUpdate({
|
||||
licenseUuid: this.instance.licenseUuid || "",
|
||||
patchedLicenseRequest: data,
|
||||
})
|
||||
: new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseCreate({
|
||||
: this.#api.enterpriseLicenseCreate({
|
||||
licenseRequest: data,
|
||||
})
|
||||
).then((data) => {
|
||||
@@ -59,19 +66,21 @@ export class EnterpriseLicenseForm extends ModelForm<License, string> {
|
||||
});
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal label=${msg("Install ID")}>
|
||||
<input
|
||||
class="pf-c-form-control pf-m-monospace"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
readonly
|
||||
type="text"
|
||||
value="${ifPresent(this.installID)}"
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
protected override renderForm(): SlottedTemplateResult {
|
||||
return html`<ak-text-input
|
||||
label=${msg("Install ID")}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
readonly
|
||||
type="text"
|
||||
name="installID"
|
||||
input-hint="code"
|
||||
value="${ifPresent(this.installID)}"
|
||||
>
|
||||
</ak-text-input>
|
||||
<ak-secret-textarea-input
|
||||
name="key"
|
||||
?required=${!this.instance}
|
||||
?revealed=${!this.instance}
|
||||
placeholder=${msg("Paste your license key...")}
|
||||
label=${msg("License key")}
|
||||
|
||||
@@ -11,11 +11,14 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { docLink } from "#common/global";
|
||||
|
||||
import { IconEditButton, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PFColor } from "#elements/Label";
|
||||
import { PaginatedResponse, TableColumn, Timestamp } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { EnterpriseLicenseForm } from "#admin/enterprise/EnterpriseLicenseForm";
|
||||
|
||||
import {
|
||||
EnterpriseApi,
|
||||
License,
|
||||
@@ -26,8 +29,8 @@ import {
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { css, CSSResult, html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { css, CSSResult, html, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
@@ -37,27 +40,7 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
|
||||
@customElement("ak-enterprise-license-list")
|
||||
export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
|
||||
protected override searchEnabled = true;
|
||||
public pageTitle = msg("Licenses");
|
||||
public pageDescription = msg("Manage enterprise licenses");
|
||||
public pageIcon = "pf-icon pf-icon-key";
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
|
||||
@state()
|
||||
forecast?: LicenseForecast;
|
||||
|
||||
@state()
|
||||
summary?: LicenseSummary;
|
||||
|
||||
@state()
|
||||
installID?: string;
|
||||
|
||||
static styles: CSSResult[] = [
|
||||
public static styles: CSSResult[] = [
|
||||
...super.styles,
|
||||
PFGrid,
|
||||
PFBanner,
|
||||
@@ -74,6 +57,25 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
`,
|
||||
];
|
||||
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
|
||||
protected override searchEnabled = true;
|
||||
public override pageTitle = msg("Licenses");
|
||||
public override pageDescription = msg("Manage enterprise licenses");
|
||||
public override pageIcon = "pf-icon pf-icon-key";
|
||||
public override searchPlaceholder = msg("Search for a license by name...");
|
||||
public override order = "name";
|
||||
|
||||
@state()
|
||||
protected forecast?: LicenseForecast;
|
||||
|
||||
@state()
|
||||
protected summary?: LicenseSummary;
|
||||
|
||||
@state()
|
||||
protected installID?: string;
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<License>> {
|
||||
this.forecast = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseForecastRetrieve();
|
||||
this.summary = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve({
|
||||
@@ -96,7 +98,7 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
|
||||
// TODO: Make this more generic, maybe automatically get the plural name
|
||||
// of the object to use in the renderEmpty
|
||||
renderEmpty(inner?: TemplateResult): TemplateResult {
|
||||
protected override renderEmpty(inner?: SlottedTemplateResult): SlottedTemplateResult {
|
||||
return super.renderEmpty(html`
|
||||
${inner
|
||||
? inner
|
||||
@@ -110,7 +112,7 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
`);
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("License(s)")}
|
||||
@@ -138,7 +140,7 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
renderSectionBefore(): TemplateResult {
|
||||
protected override renderSectionBefore(): SlottedTemplateResult {
|
||||
const {
|
||||
externalUsers = 0,
|
||||
internalUsers = 0,
|
||||
@@ -219,18 +221,9 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
html`<div>${msg(str`Internal: ${item.internalUsers}`)}</div>
|
||||
<div>${msg(str`External: ${item.externalUsers}`)}</div>`,
|
||||
html`<ak-label color=${color}> ${item.expiry?.toLocaleString()} </ak-label>`,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update License")}</span>
|
||||
<ak-enterprise-license-form slot="form" .instancePk=${item.licenseUuid}>
|
||||
</ak-enterprise-license-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(EnterpriseLicenseForm, item.licenseUuid, item.name)}
|
||||
|
||||
<ak-rbac-object-permission-modal
|
||||
model=${ModelEnum.AuthentikEnterpriseLicense}
|
||||
objectPk=${item.licenseUuid}
|
||||
@@ -240,7 +233,7 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
];
|
||||
}
|
||||
|
||||
renderGetLicenseCard() {
|
||||
protected renderGetLicenseCard() {
|
||||
const renderSpinner = () =>
|
||||
html` <div class="pf-c-card__body">
|
||||
<ak-spinner></ak-spinner>
|
||||
@@ -277,15 +270,8 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
</div> `;
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Install")}</span>
|
||||
<span slot="header">${msg("Install License")}</span>
|
||||
<ak-enterprise-license-form slot="form"> </ak-enterprise-license-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Install")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(EnterpriseLicenseForm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ export class DataExportListPage extends TablePage<DataExport> {
|
||||
</dl>`;
|
||||
}
|
||||
|
||||
protected renderEmpty(_inner?: TemplateResult): TemplateResult {
|
||||
protected renderEmpty(_inner?: TemplateResult): SlottedTemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon=${this.pageIcon}
|
||||
><span
|
||||
|
||||
@@ -16,7 +16,7 @@ import { EventGeo, renderEventUser } from "#admin/events/utils";
|
||||
import { Event, EventsApi } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, PropertyValues, TemplateResult } from "lit";
|
||||
import { html, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-object-changelog")
|
||||
@@ -82,11 +82,11 @@ export class ObjectChangelog extends Table<Event> {
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: Event): TemplateResult {
|
||||
renderExpanded(item: Event): SlottedTemplateResult {
|
||||
return html`<ak-event-info .event=${item as EventWithContext}></ak-event-info>`;
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
renderEmpty(): SlottedTemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state
|
||||
><span>${msg("No Events found.")}</span>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "#components/ak-switch-input";
|
||||
import "#components/ak-text-input";
|
||||
import "#elements/ak-dual-select/ak-dual-select-dynamic-selected-provider";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
import "#elements/forms/Radio";
|
||||
@@ -29,6 +30,9 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@customElement("ak-event-rule-form")
|
||||
export class RuleForm extends ModelForm<NotificationRule, string> {
|
||||
public static verboseName = msg("Notification Rule");
|
||||
public static verboseNamePlural = msg("Notification Rules");
|
||||
|
||||
eventTransports?: PaginatedNotificationTransportList;
|
||||
|
||||
loadInstance(pk: string): Promise<NotificationRule> {
|
||||
@@ -62,23 +66,24 @@ export class RuleForm extends ModelForm<NotificationRule, string> {
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
return html` <ak-text-input
|
||||
required
|
||||
autocomplete="off"
|
||||
name="name"
|
||||
label=${msg("Rule Name")}
|
||||
placeholder=${msg("Type a name for this rule...")}
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
></ak-text-input>
|
||||
<ak-form-element-horizontal label=${msg("Group")} name="destinationGroup">
|
||||
<ak-search-select
|
||||
placeholder=${msg("Select a group...")}
|
||||
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
||||
const args: CoreGroupsListRequest = {
|
||||
ordering: "name",
|
||||
includeUsers: false,
|
||||
};
|
||||
|
||||
if (query !== undefined) {
|
||||
if (typeof query !== "undefined") {
|
||||
args.search = query;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,13 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { severityToLabel } from "#common/labels";
|
||||
|
||||
import { IconEditButton, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { RuleForm } from "#admin/events/RuleForm";
|
||||
|
||||
import { EventsApi, ModelEnum, NotificationRule } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@@ -23,9 +26,12 @@ import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-event-rule-list")
|
||||
export class RuleListPage extends TablePage<NotificationRule> {
|
||||
expandable = true;
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
public override expandable = true;
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
public override searchPlaceholder = msg(
|
||||
"Search for a notification rule by name, severity or group...",
|
||||
);
|
||||
|
||||
protected override searchEnabled = true;
|
||||
public pageTitle = msg("Notification Rules");
|
||||
@@ -35,9 +41,9 @@ export class RuleListPage extends TablePage<NotificationRule> {
|
||||
public pageIcon = "pf-icon pf-icon-attention-bell";
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
public order = "name";
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<NotificationRule>> {
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<NotificationRule>> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsRulesList(await this.defaultEndpointConfig());
|
||||
}
|
||||
|
||||
@@ -49,7 +55,7 @@ export class RuleListPage extends TablePage<NotificationRule> {
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Notification rule(s)")}
|
||||
@@ -71,7 +77,7 @@ export class RuleListPage extends TablePage<NotificationRule> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: NotificationRule): SlottedTemplateResult[] {
|
||||
protected override row(item: NotificationRule): SlottedTemplateResult[] {
|
||||
const enabled = !!item.destinationGroupObj || item.destinationEventUser;
|
||||
return [
|
||||
html`<ak-status-label type="warning" ?good=${enabled}></ak-status-label>`,
|
||||
@@ -82,17 +88,8 @@ export class RuleListPage extends TablePage<NotificationRule> {
|
||||
>${item.destinationGroupObj.name}</a
|
||||
>`
|
||||
: msg("-")}`,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Notification Rule")}</span>
|
||||
<ak-event-rule-form slot="form" .instancePk=${item.pk}> </ak-event-rule-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(RuleForm, item.pk, item.name)}
|
||||
|
||||
<ak-rbac-object-permission-modal
|
||||
model=${ModelEnum.AuthentikEventsNotificationrule}
|
||||
@@ -103,19 +100,13 @@ export class RuleListPage extends TablePage<NotificationRule> {
|
||||
];
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("Create Notification Rule")}</span>
|
||||
<ak-event-rule-form slot="form"> </ak-event-rule-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(RuleForm);
|
||||
}
|
||||
|
||||
renderExpanded(item: NotificationRule): TemplateResult {
|
||||
protected override renderExpanded(item: NotificationRule): TemplateResult {
|
||||
const [appLabel, modelName] = ModelEnum.AuthentikEventsNotificationrule.split(".");
|
||||
|
||||
return html`<p>
|
||||
${msg(
|
||||
`These bindings control upon which events this rule triggers.
|
||||
|
||||
@@ -5,13 +5,14 @@ import { EventWithContext } from "#common/events";
|
||||
import { actionToLabel } from "#common/labels";
|
||||
|
||||
import { PaginatedResponse, RowType, Table, TableColumn, Timestamp } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { EventGeo, renderEventUser } from "#admin/events/utils";
|
||||
|
||||
import { Event, EventsApi, EventsEventsListRequest } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
import { html } from "lit-html";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
export abstract class SimpleEventTable extends Table<Event> {
|
||||
@@ -57,11 +58,11 @@ export abstract class SimpleEventTable extends Table<Event> {
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: Event): TemplateResult {
|
||||
protected override renderExpanded(item: Event): SlottedTemplateResult {
|
||||
return html`<ak-event-info .event=${item as EventWithContext}></ak-event-info>`;
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
protected override renderEmpty(): SlottedTemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state
|
||||
><span>${msg("No Events found.")}</span>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "#components/ak-hidden-text-input";
|
||||
import "#components/ak-switch-input";
|
||||
import "#components/ak-text-input";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
import "#elements/forms/Radio";
|
||||
import "#elements/forms/SearchSelect/index";
|
||||
@@ -27,6 +28,9 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@customElement("ak-event-transport-form")
|
||||
export class TransportForm extends ModelForm<NotificationTransport, string> {
|
||||
public static override verboseName = msg("Notification Transport");
|
||||
public static override verboseNamePlural = msg("Notification Transports");
|
||||
|
||||
loadInstance(pk: string): Promise<NotificationTransport> {
|
||||
return new EventsApi(DEFAULT_CONFIG)
|
||||
.eventsTransportsRetrieve({
|
||||
@@ -88,15 +92,16 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html`
|
||||
<ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
return html`<ak-text-input
|
||||
label=${msg("Transport Name")}
|
||||
placeholder=${msg("Type a name for this transport...")}
|
||||
autofocus
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
required
|
||||
name="name"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
></ak-text-input>
|
||||
<ak-switch-input
|
||||
name="sendOnce"
|
||||
label=${msg("Send once")}
|
||||
@@ -248,8 +253,7 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
||||
</option>`;
|
||||
})}
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
`;
|
||||
</ak-form-element-horizontal> `;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,18 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEditButton, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { TransportForm } from "#admin/events/TransportForm";
|
||||
|
||||
import { EventsApi, ModelEnum, NotificationTransport } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-event-transport-list")
|
||||
export class TransportListPage extends TablePage<NotificationTransport> {
|
||||
@@ -28,26 +31,28 @@ export class TransportListPage extends TablePage<NotificationTransport> {
|
||||
);
|
||||
public pageIcon = "pf-icon pf-icon-export";
|
||||
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
expandable = true;
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
public override expandable = true;
|
||||
public override searchPlaceholder = msg(
|
||||
"Search for a notification transport by name or mode...",
|
||||
);
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
public override order = "name";
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<NotificationTransport>> {
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<NotificationTransport>> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsTransportsList(
|
||||
await this.defaultEndpointConfig(),
|
||||
);
|
||||
}
|
||||
|
||||
protected columns: TableColumn[] = [
|
||||
protected override columns: TableColumn[] = [
|
||||
[msg("Name"), "name"],
|
||||
[msg("Mode"), "mode"],
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Notification transport(s)")}
|
||||
@@ -69,22 +74,12 @@ export class TransportListPage extends TablePage<NotificationTransport> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: NotificationTransport): SlottedTemplateResult[] {
|
||||
protected override row(item: NotificationTransport): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`${item.modeVerbose}`,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Notification Transport")}</span>
|
||||
<ak-event-transport-form slot="form" .instancePk=${item.pk}>
|
||||
</ak-event-transport-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
item.name,
|
||||
item.modeVerbose,
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(TransportForm, item.pk, item.name)}
|
||||
|
||||
<ak-rbac-object-permission-modal
|
||||
model=${ModelEnum.AuthentikEventsNotificationtransport}
|
||||
@@ -107,7 +102,7 @@ export class TransportListPage extends TablePage<NotificationTransport> {
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: NotificationTransport): TemplateResult {
|
||||
protected override renderExpanded(item: NotificationTransport): SlottedTemplateResult {
|
||||
const [appLabel, modelName] = ModelEnum.AuthentikEventsNotificationtransport.split(".");
|
||||
return html`<dl class="pf-c-description-list pf-m-horizontal">
|
||||
<div class="pf-c-description-list__group">
|
||||
@@ -127,15 +122,8 @@ export class TransportListPage extends TablePage<NotificationTransport> {
|
||||
</dl>`;
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("Create Notification Transport")}</span>
|
||||
<ak-event-transport-form slot="form"> </ak-event-transport-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(TransportForm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export class FileListPage extends WithCapabilitiesConfig(TablePage<FileItem>) {
|
||||
public override pageTitle = msg("Files");
|
||||
public override pageDescription = msg("Manage uploaded files.");
|
||||
public override pageIcon = "pf-icon pf-icon-folder-open";
|
||||
public override searchPlaceholder = msg("Search for a file by name...");
|
||||
|
||||
@property({ type: String, useDefault: true })
|
||||
public order: FileListOrderKey = "name";
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import "#admin/flows/StageBindingForm";
|
||||
import "#admin/policies/BoundPoliciesList";
|
||||
import "#admin/rbac/ObjectPermissionModal";
|
||||
import "#admin/stages/StageWizard";
|
||||
import "#elements/Tabs";
|
||||
import "#elements/forms/DeleteBulkForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { modalInvoker } from "#elements/dialogs";
|
||||
import { IconPermissionButton } from "#elements/dialogs/components/IconPermissionButton";
|
||||
import { CustomFormElementTagName } from "#elements/forms/unsafe";
|
||||
import { PaginatedResponse, Table, TableColumn } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
|
||||
import { StageBindingForm } from "#admin/flows/StageBindingForm";
|
||||
import { AKStageWizard } from "#admin/stages/ak-stage-wizard";
|
||||
|
||||
import { FlowsApi, FlowStageBinding, ModelEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@customElement("ak-bound-stages-list")
|
||||
export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
@@ -80,39 +83,56 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
row(item: FlowStageBinding): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`<pre>${item.order}</pre>`,
|
||||
html`${item.stageObj?.name}`,
|
||||
html`${item.stageObj?.verboseName}`,
|
||||
html` <ak-forms-modal>
|
||||
${StrictUnsafe<CustomFormElementTagName>(item.stageObj?.component, {
|
||||
slot: "form",
|
||||
instancePk: item.stageObj?.pk,
|
||||
submitLabel: msg("Save Changes"),
|
||||
headline: msg(str`Update ${item.stageObj?.verboseName}`, {
|
||||
id: "form.headline.update",
|
||||
item.stageObj?.name,
|
||||
item.stageObj?.verboseName,
|
||||
html`<div class="ak-c-table__actions">
|
||||
<button
|
||||
type="button"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
${modalInvoker(() =>
|
||||
StrictUnsafe<CustomFormElementTagName>(item.stageObj?.component, {
|
||||
instancePk: item.stageObj?.pk,
|
||||
}),
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${msg("Edit Stage")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Stage binding")}</span>
|
||||
<ak-stage-binding-form slot="form" .instancePk=${item.pk}>
|
||||
</ak-stage-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${msg("Edit Binding")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-rbac-object-permission-modal
|
||||
model=${ModelEnum.AuthentikFlowsFlowstagebinding}
|
||||
objectPk=${item.pk}
|
||||
)}
|
||||
>
|
||||
</ak-rbac-object-permission-modal>`,
|
||||
${msg("Edit Stage")}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
${modalInvoker(StageBindingForm, { instancePk: item.pk })}
|
||||
>
|
||||
${msg("Edit Binding")}
|
||||
</button>
|
||||
${IconPermissionButton(item.stageObj?.name || "", {
|
||||
model: ModelEnum.AuthentikFlowsFlowstagebinding,
|
||||
objectPk: item.pk,
|
||||
})}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: FlowStageBinding): TemplateResult {
|
||||
protected renderActions(): SlottedTemplateResult {
|
||||
return html`<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
${modalInvoker(AKStageWizard, {
|
||||
showBindingPage: true,
|
||||
bindingTarget: this.target,
|
||||
})}
|
||||
>
|
||||
${msg("New Stage")}
|
||||
</button>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-primary"
|
||||
${modalInvoker(StageBindingForm, { targetPk: this.target })}
|
||||
>
|
||||
${msg("Bind Existing Stage")}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
protected override renderExpanded(item: FlowStageBinding): TemplateResult {
|
||||
return html`<div class="pf-c-content">
|
||||
<p>${msg("These bindings control if this stage will be applied to the flow.")}</p>
|
||||
<ak-bound-policies-list
|
||||
@@ -123,49 +143,18 @@ export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
protected override renderEmpty(): SlottedTemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon="pf-icon-module">
|
||||
<span>${msg("No Stages bound")}</span>
|
||||
<div slot="body">${msg("No stages are currently bound to this flow.")}</div>
|
||||
<div slot="primary">
|
||||
<ak-stage-wizard
|
||||
createText=${msg("Create and bind Stage")}
|
||||
showBindingPage
|
||||
bindingTarget=${ifDefined(this.target)}
|
||||
></ak-stage-wizard>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("Create Stage binding")}</span>
|
||||
<ak-stage-binding-form slot="form" targetPk=${ifDefined(this.target)}>
|
||||
</ak-stage-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${msg("Bind existing Stage")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
</div>
|
||||
<div slot="primary">${this.renderActions()}</div>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html`
|
||||
<ak-stage-wizard
|
||||
createText=${msg("Create and bind Stage")}
|
||||
showBindingPage
|
||||
bindingTarget=${ifDefined(this.target)}
|
||||
></ak-stage-wizard>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("Create Stage binding")}</span>
|
||||
<ak-stage-binding-form slot="form" targetPk=${ifDefined(this.target)}>
|
||||
</ak-stage-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${msg("Bind existing Stage")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
${super.renderToolbar()}
|
||||
`;
|
||||
protected override renderToolbar(): SlottedTemplateResult {
|
||||
return [this.renderActions(), super.renderToolbar()];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "#components/ak-file-search-input";
|
||||
import "#components/ak-slug-input";
|
||||
import "#components/ak-text-input";
|
||||
import "#components/ak-switch-input";
|
||||
import "#elements/forms/FormGroup";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
@@ -10,6 +11,8 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { ModelForm } from "#elements/forms/ModelForm";
|
||||
import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
|
||||
|
||||
import { AKLabel } from "#components/ak-label";
|
||||
|
||||
import { DesignationToLabel, LayoutToLabel } from "#admin/flows/utils";
|
||||
import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
|
||||
|
||||
@@ -35,62 +38,80 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
*/
|
||||
@customElement("ak-flow-form")
|
||||
export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
async loadInstance(pk: string): Promise<Flow> {
|
||||
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesRetrieve({
|
||||
public static override verboseName = msg("Flow");
|
||||
public static override verboseNamePlural = msg("Flows");
|
||||
|
||||
#api = new FlowsApi(DEFAULT_CONFIG);
|
||||
|
||||
protected override async loadInstance(pk: string): Promise<Flow> {
|
||||
return this.#api.flowsInstancesRetrieve({
|
||||
slug: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
public override getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated flow.")
|
||||
: msg("Successfully created flow.");
|
||||
}
|
||||
|
||||
async send(data: Flow): Promise<void | Flow> {
|
||||
protected override async send(data: Flow): Promise<void | Flow> {
|
||||
if (this.instance) {
|
||||
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesUpdate({
|
||||
return this.#api.flowsInstancesUpdate({
|
||||
slug: this.instance.slug,
|
||||
flowRequest: data,
|
||||
});
|
||||
}
|
||||
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesCreate({
|
||||
|
||||
return this.#api.flowsInstancesCreate({
|
||||
flowRequest: data,
|
||||
});
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Title")} required name="title">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.title)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">${msg("Shown as the Title in Flow pages.")}</p>
|
||||
</ak-form-element-horizontal>
|
||||
return html`<ak-text-input
|
||||
label=${msg("Flow Name")}
|
||||
placeholder=${msg("Type a name for this flow...")}
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
required
|
||||
name="name"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
></ak-text-input>
|
||||
<ak-text-input
|
||||
label=${msg("Title")}
|
||||
placeholder=${msg("Type a title for this flow...")}
|
||||
help=${msg("Shown as the Title in Flow pages.")}
|
||||
autocomplete="off"
|
||||
required
|
||||
name="title"
|
||||
value="${ifDefined(this.instance?.title)}"
|
||||
></ak-text-input>
|
||||
|
||||
<ak-slug-input
|
||||
name="slug"
|
||||
value=${ifDefined(this.instance?.slug)}
|
||||
placeholder=${msg("e.g. my-flow")}
|
||||
label=${msg("Slug")}
|
||||
required
|
||||
help=${msg("Visible in the URL.")}
|
||||
input-hint="code"
|
||||
></ak-slug-input>
|
||||
|
||||
<ak-form-element-horizontal label=${msg("Designation")} required name="designation">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.designation === undefined}>
|
||||
---------
|
||||
<ak-form-element-horizontal required name="designation">
|
||||
${AKLabel(
|
||||
{
|
||||
slot: "label",
|
||||
className: "pf-c-form__group-label",
|
||||
htmlFor: "designation",
|
||||
required: true,
|
||||
},
|
||||
msg("Designation"),
|
||||
)}
|
||||
|
||||
<select id="designation" class="pf-c-form-control" required>
|
||||
<option value="" ?selected=${!this.instance?.designation}>
|
||||
${msg("Select a designation...")}
|
||||
</option>
|
||||
<option
|
||||
value=${FlowDesignationEnum.Authentication}
|
||||
@@ -144,12 +165,18 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Authentication")}
|
||||
required
|
||||
name="authentication"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<ak-form-element-horizontal required name="authentication">
|
||||
${AKLabel(
|
||||
{
|
||||
slot: "label",
|
||||
className: "pf-c-form__group-label",
|
||||
htmlFor: "authentication",
|
||||
required: true,
|
||||
},
|
||||
msg("Authentication"),
|
||||
)}
|
||||
|
||||
<select id="authentication" class="pf-c-form-control" required>
|
||||
<option
|
||||
value=${AuthenticationEnum.None}
|
||||
?selected=${this.instance?.authentication === AuthenticationEnum.None}
|
||||
@@ -261,8 +288,17 @@ export class FlowForm extends WithCapabilitiesConfig(ModelForm<Flow, string>) {
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("Appearance settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Layout")} required name="layout">
|
||||
<select class="pf-c-form-control">
|
||||
<ak-form-element-horizontal required name="layout">
|
||||
${AKLabel(
|
||||
{
|
||||
slot: "label",
|
||||
className: "pf-c-form__group-label",
|
||||
htmlFor: "layout",
|
||||
required: true,
|
||||
},
|
||||
msg("Layout"),
|
||||
)}
|
||||
<select id="layout" class="pf-c-form-control" required>
|
||||
<option
|
||||
value=${FlowLayoutEnum.Stacked}
|
||||
?selected=${this.instance?.layout === FlowLayoutEnum.Stacked}
|
||||
|
||||
@@ -10,17 +10,19 @@ import { AndNext, DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { docLink } from "#common/global";
|
||||
import { groupBy } from "#common/utils";
|
||||
|
||||
import { IconEditButton, modalInvoker, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { FlowForm } from "#admin/flows/FlowForm";
|
||||
import { DesignationToLabel } from "#admin/flows/utils";
|
||||
|
||||
import { Flow, FlowsApi } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
|
||||
@@ -29,17 +31,18 @@ export class FlowListPage extends TablePage<Flow> {
|
||||
static styles = [...super.styles, PFBanner];
|
||||
|
||||
protected override searchEnabled = true;
|
||||
public pageTitle = msg("Flows");
|
||||
public pageDescription = msg(
|
||||
public override searchPlaceholder = msg("Search for a flow by name or identifier...");
|
||||
|
||||
public override pageTitle = msg("Flows");
|
||||
public override pageDescription = msg(
|
||||
"Flows describe a chain of Stages to authenticate, enroll or recover a user. Stages are chosen based on policies applied to them.",
|
||||
);
|
||||
public pageIcon = "pf-icon pf-icon-process-automation";
|
||||
public override pageIcon = "pf-icon pf-icon-process-automation";
|
||||
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
|
||||
@property()
|
||||
order = "slug";
|
||||
public override order = "slug";
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<Flow>> {
|
||||
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(await this.defaultEndpointConfig());
|
||||
@@ -90,21 +93,8 @@ export class FlowListPage extends TablePage<Flow> {
|
||||
item.name,
|
||||
Array.from(item.stages || []).length,
|
||||
Array.from(item.policies || []).length,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Flow")}</span>
|
||||
<ak-flow-form slot="form" .instancePk=${item.slug}> </ak-flow-form>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-plain"
|
||||
aria-label=${msg(str`Edit "${item.name}"`)}
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(FlowForm, item.slug, item.name)}
|
||||
<button
|
||||
aria-label=${msg(str`Execute "${item.name}"`)}
|
||||
class="pf-c-button pf-m-plain"
|
||||
@@ -132,39 +122,37 @@ export class FlowListPage extends TablePage<Flow> {
|
||||
];
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create Flow")}</span>
|
||||
<span slot="header">${msg("New Flow")}</span>
|
||||
<ak-flow-form slot="form"> </ak-flow-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("New Flow")}</button>
|
||||
</ak-forms-modal>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Import")}</span>
|
||||
<span slot="header">${msg("Import Flow")}</span>
|
||||
<ak-blueprint-import-form slot="form">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href=${docLink("/add-secure-apps/flows-stages/flow/examples/flows/")}
|
||||
slot="read-more-link"
|
||||
>${msg("Flow Examples")}</a
|
||||
>
|
||||
<span slot="banner-warning">
|
||||
${msg(
|
||||
"Warning: Flow imports are blueprint files, which may contain objects other than flows (such as users, policies, etc).",
|
||||
)}<br />${msg(
|
||||
"You should only import files from trusted sources and review blueprints before importing them.",
|
||||
)}
|
||||
</span>
|
||||
</ak-blueprint-import-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Import")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected renderObjectCreate(): SlottedTemplateResult {
|
||||
return [
|
||||
ModalInvokerButton(FlowForm),
|
||||
html`<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="button"
|
||||
${modalInvoker(() => {
|
||||
return html`<ak-blueprint-import-form>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href=${docLink("/add-secure-apps/flows-stages/flow/examples/flows/")}
|
||||
slot="read-more-link"
|
||||
>${msg("Flow Examples")}</a
|
||||
>
|
||||
<span slot="banner-warning">
|
||||
${msg(
|
||||
"Warning: Flow imports are blueprint files, which may contain objects other than flows (such as users, policies, etc).",
|
||||
)}<br />${msg(
|
||||
"You should only import files from trusted sources and review blueprints before importing them.",
|
||||
)}
|
||||
</span>
|
||||
</ak-blueprint-import-form>`;
|
||||
})}
|
||||
>
|
||||
${msg("Import")}
|
||||
</button>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
protected renderToolbar(): SlottedTemplateResult {
|
||||
return html`
|
||||
${super.renderToolbar()}
|
||||
<ak-forms-confirm
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, nothing, TemplateResult } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
function createInvalidResponseOptions(): RadioOption<InvalidResponseActionEnum>[] {
|
||||
@@ -52,6 +52,9 @@ function createInvalidResponseOptions(): RadioOption<InvalidResponseActionEnum>[
|
||||
|
||||
@customElement("ak-stage-binding-form")
|
||||
export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
|
||||
public static override verboseName = msg("Stage Binding");
|
||||
public static override verboseNamePlural = msg("Stage Bindings");
|
||||
|
||||
async load() {
|
||||
this.defaultOrder = await this.getOrder();
|
||||
}
|
||||
@@ -124,10 +127,11 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html` ${this.renderTarget()}
|
||||
protected override renderForm(): SlottedTemplateResult {
|
||||
return html`${this.renderTarget()}
|
||||
<ak-form-element-horizontal label=${msg("Stage")} required name="stage">
|
||||
<ak-search-select
|
||||
placeholder=${msg("Select a stage...")}
|
||||
.fetchObjects=${async (query?: string): Promise<Stage[]> => {
|
||||
const args: StagesAllListRequest = {
|
||||
ordering: "name",
|
||||
|
||||
@@ -7,6 +7,7 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEditButton, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
@@ -83,24 +84,14 @@ export class GroupListPage extends TablePage<Group> {
|
||||
>`,
|
||||
html`${Array.from(item.users || []).length}`,
|
||||
html`<ak-status-label type="neutral" ?good=${item.isSuperuser}></ak-status-label>`,
|
||||
html`<div>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
aria-label=${msg(str`Edit "${item.name}"`)}
|
||||
${GroupForm.asEditModalInvoker(item.pk)}
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(GroupForm, item.pk, item.name)}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
protected renderObjectCreate(): TemplateResult {
|
||||
return html`<button class="pf-c-button pf-m-primary" ${GroupForm.asModalInvoker()}>
|
||||
${msg("New Group")}
|
||||
</button>`;
|
||||
protected renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(GroupForm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { renderModal } from "#elements/dialogs";
|
||||
import { AKFormSubmitEvent, Form } from "#elements/forms/Form";
|
||||
import { renderModal } from "#elements/modals/utils";
|
||||
import { PaginatedResponse, Table, TableColumn } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
@@ -52,7 +52,7 @@ export class RelatedGroupAdd extends Form<{ groups: string[] }> {
|
||||
return renderModal(html`
|
||||
<ak-form
|
||||
headline=${msg("Select Groups")}
|
||||
action-label=${msg("Confirm")}
|
||||
submit-label=${msg("Confirm")}
|
||||
@submit=${(event: AKFormSubmitEvent<Group[]>) => {
|
||||
this.groupsToAdd = event.target.toJSON();
|
||||
}}
|
||||
@@ -125,7 +125,7 @@ export class RelatedGroupList extends Table<Group> {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Group(s)")}
|
||||
action-label=${msg("Remove from Group(s)")}
|
||||
submit-label=${msg("Remove from Group(s)")}
|
||||
action-subtext=${msg(
|
||||
str`Are you sure you want to remove user ${this.targetUser?.username} from the following groups?`,
|
||||
)}
|
||||
|
||||
@@ -15,10 +15,10 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEditButton, renderModal } from "#elements/dialogs";
|
||||
import { AKFormSubmitEvent, Form } from "#elements/forms/Form";
|
||||
import { WithBrandConfig } from "#elements/mixins/branding";
|
||||
import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
|
||||
import { renderModal } from "#elements/modals/utils";
|
||||
import { getURLParam, updateURLParams } from "#elements/router/RouteMatch";
|
||||
import { PaginatedResponse, Table, TableColumn, Timestamp } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
@@ -26,9 +26,9 @@ import { UserOption } from "#elements/user/utils";
|
||||
|
||||
import { AKLabel } from "#components/ak-label";
|
||||
|
||||
import { RecoveryButtons } from "#admin/users/recovery";
|
||||
import { UserForm } from "#admin/users/UserForm";
|
||||
import { UserImpersonateForm } from "#admin/users/UserImpersonateForm";
|
||||
import { renderRecoveryButtons } from "#admin/users/UserListPage";
|
||||
|
||||
import {
|
||||
CapabilitiesEnum,
|
||||
@@ -91,11 +91,15 @@ export class AddRelatedUserForm extends Form<{ users: number[] }> {
|
||||
return data;
|
||||
}
|
||||
|
||||
protected openUserSelectionModal = () => {
|
||||
protected openUserSelectionModal = (event?: Event) => {
|
||||
if (event?.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
return renderModal(html`
|
||||
<ak-form
|
||||
headline=${msg("Select users")}
|
||||
action-label=${msg("Confirm")}
|
||||
submit-label=${msg("Confirm")}
|
||||
@submit=${(event: AKFormSubmitEvent<User[]>) => {
|
||||
this.usersToAdd = event.target.toJSON();
|
||||
}}
|
||||
@@ -111,7 +115,7 @@ export class AddRelatedUserForm extends Form<{ users: number[] }> {
|
||||
// table to allow the table to appear as an inline-block element next to the input group.
|
||||
// This should be fixed by moving the `@container` query off `:host`.
|
||||
|
||||
return html` <ak-form-element-horizontal name="users">
|
||||
return html`<ak-form-element-horizontal name="users">
|
||||
${AKLabel(
|
||||
{
|
||||
slot: "label",
|
||||
@@ -130,14 +134,16 @@ export class AddRelatedUserForm extends Form<{ users: number[] }> {
|
||||
aria-label=${msg("Open user selection dialog")}
|
||||
@click=${this.openUserSelectionModal}
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Add users")}>
|
||||
<pf-tooltip position="right" content=${msg("Add users")}>
|
||||
<i class="fas fa-plus" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-form-control">
|
||||
<ak-chip-group>
|
||||
${this.usersToAdd.map((user) => {
|
||||
<ak-chip-group
|
||||
@click=${this.openUserSelectionModal}
|
||||
placeholder=${msg("Select one or more users to assign...")}
|
||||
>${this.usersToAdd.map((user) => {
|
||||
return html`<ak-chip
|
||||
removable
|
||||
value=${ifDefined(user.pk)}
|
||||
@@ -149,8 +155,8 @@ export class AddRelatedUserForm extends Form<{ users: number[] }> {
|
||||
>
|
||||
${UserOption(user)}
|
||||
</ak-chip>`;
|
||||
})}
|
||||
</ak-chip-group>
|
||||
})}</ak-chip-group
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</ak-form-element-horizontal>`;
|
||||
@@ -222,7 +228,7 @@ export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Tabl
|
||||
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("User(s)")}
|
||||
action-label=${msg("Remove User(s)")}
|
||||
submit-label=${msg("Remove User(s)")}
|
||||
action=${msg("removed")}
|
||||
action-subtext=${targetLabel
|
||||
? msg(str`Are you sure you want to remove the selected users from ${targetLabel}?`)
|
||||
@@ -271,16 +277,12 @@ export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Tabl
|
||||
html`<ak-status-label ?good=${item.isActive}></ak-status-label>`,
|
||||
Timestamp(item.lastLogin),
|
||||
|
||||
html`<div>
|
||||
<button class="pf-c-button pf-m-plain" ${UserForm.asEditModalInvoker(item.pk)}>
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(UserForm, item.pk)}
|
||||
${showImpersonate
|
||||
? html`<button
|
||||
class="pf-c-button pf-m-tertiary"
|
||||
${UserImpersonateForm.asEditModalInvoker(item.pk)}
|
||||
${UserImpersonateForm.asInstanceInvoker(item.pk)}
|
||||
>
|
||||
<pf-tooltip
|
||||
position="top"
|
||||
@@ -340,7 +342,7 @@ export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Tabl
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${renderRecoveryButtons({
|
||||
${RecoveryButtons({
|
||||
user: item,
|
||||
brandHasRecoveryFlow: Boolean(this.brand.flowRecovery),
|
||||
})}
|
||||
|
||||
@@ -43,8 +43,8 @@ export class GroupForm extends ModelForm<Group, string> {
|
||||
`,
|
||||
];
|
||||
|
||||
public entitySingular = msg("Group");
|
||||
public entityPlural = msg("Groups");
|
||||
public static verboseName = msg("Group");
|
||||
public static verboseNamePlural = msg("Groups");
|
||||
|
||||
#fetchGroups = (page: number, search?: string): Promise<DataProvision> => {
|
||||
return new CoreApi(DEFAULT_CONFIG)
|
||||
|
||||
@@ -84,6 +84,9 @@ function formatContentTypePlaceholder(contentType: ContentTypeEnum): string {
|
||||
|
||||
@customElement("ak-lifecycle-rule-form")
|
||||
export class LifecycleRuleForm extends ModelForm<LifecycleRule, string, LifecycleRule | null> {
|
||||
public static override verboseName = msg("Lifecycle Rule");
|
||||
public static override verboseNamePlural = msg("Lifecycle Rules");
|
||||
|
||||
#targetSelectRef = createRef<SearchSelect<TargetObject>>();
|
||||
#reviewerGroupsSelectRef = createRef<SearchSelect<Group>>();
|
||||
#reviewerUsersSelectRef = createRef<SearchSelect<Group>>();
|
||||
|
||||
@@ -11,14 +11,17 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEditButton, ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { LifecycleRuleForm } from "#admin/lifecycle/LifecycleRuleForm";
|
||||
|
||||
import { LifecycleApi, LifecycleRule, ModelEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-lifecycle-rule-list")
|
||||
@@ -26,10 +29,10 @@ export class LifecycleRuleListPage extends TablePage<LifecycleRule> {
|
||||
public override expandable = true;
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
|
||||
public pageTitle = msg("Object Lifecycle Rules");
|
||||
public pageDescription = msg("Schedule periodic reviews for objects in authentik.");
|
||||
public pageIcon = "pf-icon pf-icon-history";
|
||||
public override searchPlaceholder = msg("Search for a lifecycle rule by name or target...");
|
||||
public override pageTitle = msg("Object Lifecycle Rules");
|
||||
public override pageDescription = msg("Schedule periodic reviews for objects in authentik.");
|
||||
public override pageIcon = "pf-icon pf-icon-history";
|
||||
|
||||
public override order = "name";
|
||||
|
||||
@@ -41,11 +44,11 @@ export class LifecycleRuleListPage extends TablePage<LifecycleRule> {
|
||||
);
|
||||
}
|
||||
|
||||
protected renderSectionBefore(): TemplateResult {
|
||||
protected override renderSectionBefore(): SlottedTemplateResult {
|
||||
return html`<ak-lifecycle-preview-banner></ak-lifecycle-preview-banner>`;
|
||||
}
|
||||
|
||||
protected columns: TableColumn[] = [
|
||||
protected override columns: TableColumn[] = [
|
||||
[msg("Name"), "name"],
|
||||
[msg("Target"), "content_type__model"],
|
||||
[msg("Interval"), "interval"],
|
||||
@@ -53,7 +56,7 @@ export class LifecycleRuleListPage extends TablePage<LifecycleRule> {
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html` <ak-forms-delete-bulk
|
||||
object-label=${msg("Lifecycle rule(s)")}
|
||||
@@ -74,26 +77,14 @@ export class LifecycleRuleListPage extends TablePage<LifecycleRule> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: LifecycleRule): SlottedTemplateResult[] {
|
||||
protected override row(item: LifecycleRule): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`${item.targetVerbose}`,
|
||||
html`${item.interval}`,
|
||||
html`${item.gracePeriod}`,
|
||||
html` <div>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Lifecycle Rule")}</span>
|
||||
<ak-lifecycle-rule-form
|
||||
slot="form"
|
||||
.instancePk=${item.id}
|
||||
></ak-lifecycle-rule-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
item.name,
|
||||
item.targetVerbose,
|
||||
item.interval || msg("-"),
|
||||
item.gracePeriod || msg("-"),
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(LifecycleRuleForm, item.id, item.name)}
|
||||
|
||||
<ak-rbac-object-permission-modal
|
||||
model=${ModelEnum.AuthentikLifecycleLifecyclerule}
|
||||
@@ -104,7 +95,7 @@ export class LifecycleRuleListPage extends TablePage<LifecycleRule> {
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: LifecycleRule): TemplateResult {
|
||||
protected override renderExpanded(item: LifecycleRule): SlottedTemplateResult {
|
||||
const [appLabel, modelName] = ModelEnum.AuthentikLifecycleLifecyclerule.split(".");
|
||||
return html`<dl class="pf-c-description-list pf-m-horizontal">
|
||||
<div class="pf-c-description-list__group">
|
||||
@@ -114,6 +105,7 @@ export class LifecycleRuleListPage extends TablePage<LifecycleRule> {
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ak-task-list
|
||||
search-placeholder=${msg("Search tasks...")}
|
||||
.relObjAppLabel=${appLabel}
|
||||
.relObjModel=${modelName}
|
||||
.relObjId="${item.id}"
|
||||
@@ -123,16 +115,8 @@ export class LifecycleRuleListPage extends TablePage<LifecycleRule> {
|
||||
</div>
|
||||
</dl>`;
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Create")}</span>
|
||||
<span slot="header">${msg("Create Object Lifecycle Rule")}</span>
|
||||
<ak-lifecycle-rule-form slot="form"></ak-lifecycle-rule-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||
</ak-forms-modal>
|
||||
`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return ModalInvokerButton(LifecycleRuleForm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { createPaginatedResponse } from "#common/api/responses";
|
||||
import { isResponseErrorLike } from "#common/errors/network";
|
||||
|
||||
import { ModalInvokerButton } from "#elements/dialogs";
|
||||
import { PaginatedResponse, Table, TableColumn, Timestamp } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { ifPreviousValue } from "#elements/utils/properties";
|
||||
|
||||
import { ObjectReviewForm } from "#admin/lifecycle/ObjectReviewForm";
|
||||
import { LifecycleIterationStatus } from "#admin/lifecycle/utils";
|
||||
|
||||
import {
|
||||
@@ -257,7 +259,7 @@ export class ObjectLifecyclePage extends Table<Review> {
|
||||
];
|
||||
}
|
||||
|
||||
protected override renderEmpty(): TemplateResult {
|
||||
protected override renderEmpty(): SlottedTemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon="pf-icon-task"
|
||||
><span>${this.emptyStateMessage}</span></ak-empty-state
|
||||
@@ -267,18 +269,12 @@ export class ObjectLifecyclePage extends Table<Review> {
|
||||
|
||||
protected renderObjectCreate(): SlottedTemplateResult {
|
||||
if (!this.iteration?.userCanReview) {
|
||||
return nothing;
|
||||
return null;
|
||||
}
|
||||
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit">${msg("Confirm Review")}</span>
|
||||
<span slot="header">${msg("Confirm this object has been reviewed")}</span>
|
||||
<ak-object-review-form slot="form" .iteration=${this.iteration}>
|
||||
</ak-object-review-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${msg("Confirm Review")}
|
||||
</button>
|
||||
</ak-forms-modal>`;
|
||||
return ModalInvokerButton(ObjectReviewForm, {
|
||||
iteration: this.iteration,
|
||||
});
|
||||
}
|
||||
|
||||
protected override render(): SlottedTemplateResult {
|
||||
|
||||
@@ -13,6 +13,11 @@ import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-object-review-form")
|
||||
export class ObjectReviewForm extends ModelForm<Review, string, Review | null> {
|
||||
public static override verboseName = msg("Review");
|
||||
public static override verboseNamePlural = msg("Reviews");
|
||||
public static override submitVerb = msg("Confirm");
|
||||
public static override createLabel = msg("Confirm");
|
||||
|
||||
@property({ attribute: false })
|
||||
public iteration: LifecycleIteration | null = null;
|
||||
|
||||
|
||||
@@ -95,19 +95,19 @@ function providerProvider(type: OutpostTypeEnum): DataProvider {
|
||||
|
||||
@customElement("ak-outpost-form")
|
||||
export class OutpostForm extends ModelForm<Outpost, string> {
|
||||
public entitySingular = msg("Outpost");
|
||||
public entityPlural = msg("Outposts");
|
||||
public static verboseName = msg("Outpost");
|
||||
public static verboseNamePlural = msg("Outposts");
|
||||
|
||||
@property()
|
||||
type: OutpostTypeEnum = OutpostTypeEnum.Proxy;
|
||||
@property({ type: String })
|
||||
public type: OutpostTypeEnum = OutpostTypeEnum.Proxy;
|
||||
|
||||
@property({ type: Boolean })
|
||||
embedded = false;
|
||||
public embedded = false;
|
||||
|
||||
@state()
|
||||
providers: DataProvider = providerProvider(this.type);
|
||||
protected providers: DataProvider = providerProvider(this.type);
|
||||
|
||||
defaultConfig?: OutpostDefaultConfig;
|
||||
protected defaultConfig?: OutpostDefaultConfig;
|
||||
|
||||
public override reset(): void {
|
||||
super.reset();
|
||||
|
||||
@@ -7,6 +7,7 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { IconEditButton } from "#elements/dialogs";
|
||||
import { PFColor } from "#elements/Label";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
@@ -26,7 +27,9 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
||||
export class OutpostListPage extends TablePage<Outpost> {
|
||||
protected override searchEnabled = true;
|
||||
|
||||
public override searchPlaceholder = msg("Search outposts...");
|
||||
public override searchPlaceholder = msg(
|
||||
"Search outposts by name, type or assigned integration...",
|
||||
);
|
||||
public override pageTitle = msg("Outposts");
|
||||
public override pageDescription = msg(
|
||||
"Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies.",
|
||||
@@ -70,19 +73,6 @@ export class OutpostListPage extends TablePage<Outpost> {
|
||||
@property({ type: String })
|
||||
public order = "name";
|
||||
|
||||
protected openEditModal = (event: Event) => {
|
||||
const button = event.currentTarget as HTMLButtonElement;
|
||||
const instancePk = button.dataset.instancePk!;
|
||||
const managed = button.dataset.managed === "true";
|
||||
|
||||
const form = new OutpostForm();
|
||||
|
||||
form.instancePk = instancePk;
|
||||
form.embedded = managed;
|
||||
|
||||
return form.showModal();
|
||||
};
|
||||
|
||||
protected renderItemProviders(item: Outpost) {
|
||||
if (item.providers.length < 1) {
|
||||
return html`-`;
|
||||
@@ -116,17 +106,11 @@ export class OutpostListPage extends TablePage<Outpost> {
|
||||
html`<ak-outpost-health-simple
|
||||
outpostId=${ifDefined(item.pk)}
|
||||
></ak-outpost-health-simple>`,
|
||||
html`<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
aria-label=${msg(str`Edit ${item.name}`)}
|
||||
data-instance-pk=${item.pk}
|
||||
data-managed=${item.managed === embeddedOutpostManaged}
|
||||
@click=${this.openEditModal}
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>`,
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButton(OutpostForm, item.pk, item.name, {
|
||||
embedded: item.managed === embeddedOutpostManaged,
|
||||
})}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { docLink } from "#common/global";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { IconTokenCopyButton } from "#elements/buttons/IconTokenCopyButton";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { setPageDetails } from "#components/ak-page-navbar";
|
||||
@@ -27,7 +28,6 @@ import { CSSResult, PropertyValues } from "lit";
|
||||
import { html } from "lit-html";
|
||||
import { guard } from "lit-html/directives/guard.js";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
@@ -242,14 +242,7 @@ export class OutpostViewPage extends AKElement {
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">AUTHENTIK_TOKEN</span>
|
||||
</label>
|
||||
<div>
|
||||
<ak-token-copy-button
|
||||
class="pf-m-primary"
|
||||
identifier="${ifDefined(this.outpost?.tokenIdentifier)}"
|
||||
>
|
||||
${msg("Click to copy token")}
|
||||
</ak-token-copy-button>
|
||||
</div>
|
||||
<div>${IconTokenCopyButton(this.outpost?.tokenIdentifier)}</div>
|
||||
</div>
|
||||
<h3>
|
||||
${msg(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "#admin/outposts/ServiceConnectionDockerForm";
|
||||
import "#admin/outposts/ServiceConnectionKubernetesForm";
|
||||
import "#admin/outposts/ServiceConnectionWizard";
|
||||
import "#admin/outposts/ak-service-connection-wizard";
|
||||
import "#admin/rbac/ObjectPermissionModal";
|
||||
import "#components/ak-status-label";
|
||||
import "#elements/buttons/SpinnerButton/index";
|
||||
@@ -12,16 +12,23 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { CustomFormElementTagName } from "#elements/forms/unsafe";
|
||||
import { IconEditButtonByTagName } from "#elements/dialogs";
|
||||
import { IconPermissionButton } from "#elements/dialogs/components/IconPermissionButton";
|
||||
import { PFColor } from "#elements/Label";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
|
||||
import { OutpostsApi, ServiceConnection, ServiceConnectionState } from "@goauthentik/api";
|
||||
import { AKServiceConnectionWizard } from "#admin/outposts/ak-service-connection-wizard";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import {
|
||||
ModelEnum,
|
||||
OutpostsApi,
|
||||
ServiceConnection,
|
||||
ServiceConnectionState,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
@@ -36,9 +43,12 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
|
||||
public pageIcon = "pf-icon pf-icon-integration";
|
||||
protected override searchEnabled = true;
|
||||
|
||||
checkbox = true;
|
||||
expandable = true;
|
||||
clearOnRefresh = true;
|
||||
public override checkbox = true;
|
||||
public override expandable = true;
|
||||
public override clearOnRefresh = true;
|
||||
public override searchPlaceholder = msg(
|
||||
"Search for an outpost integration by name, type or assigned integration...",
|
||||
);
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<ServiceConnection>> {
|
||||
const connections = await new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList(
|
||||
@@ -75,31 +85,19 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
|
||||
row(item: ServiceConnection): SlottedTemplateResult[] {
|
||||
const itemState = this.state[item.pk];
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`${item.verboseName}`,
|
||||
item.name,
|
||||
item.verboseName,
|
||||
html`<ak-status-label type="info" ?good=${item.local}></ak-status-label>`,
|
||||
html`${itemState?.healthy
|
||||
? html`<ak-label color=${PFColor.Green}>${ifDefined(itemState.version)}</ak-label>`
|
||||
: html`<ak-label color=${PFColor.Red}>${msg("Unhealthy")}</ak-label>`}`,
|
||||
html`
|
||||
<ak-forms-modal>
|
||||
${StrictUnsafe<CustomFormElementTagName>(item.component, {
|
||||
slot: "form",
|
||||
instancePk: item.pk,
|
||||
submitLabel: msg("Save Changes"),
|
||||
headline: msg(str`Update ${item.verboseName}`, {
|
||||
id: "form.headline.update",
|
||||
}),
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-rbac-object-permission-modal model=${item.metaModelName} objectPk=${item.pk}>
|
||||
</ak-rbac-object-permission-modal>
|
||||
`,
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButtonByTagName(item.component, item.pk, item.verboseName)}
|
||||
${IconPermissionButton(item.name, {
|
||||
model: item.metaModelName as ModelEnum,
|
||||
objectPk: item.pk,
|
||||
})}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -161,8 +159,15 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`<ak-service-connection-wizard></ak-service-connection-wizard> `;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return html`<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="button"
|
||||
aria-description="${msg("Open the wizard to create a new service connection.")}"
|
||||
${AKServiceConnectionWizard.asModalInvoker()}
|
||||
>
|
||||
${msg("New Outpost Integration")}
|
||||
</button>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import "#admin/outposts/ServiceConnectionDockerForm";
|
||||
import "#admin/outposts/ServiceConnectionKubernetesForm";
|
||||
import "#elements/wizard/FormWizardPage";
|
||||
import "#elements/wizard/TypeCreateWizardPage";
|
||||
import "#elements/wizard/Wizard";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
import type { Wizard } from "#elements/wizard/Wizard";
|
||||
|
||||
import { OutpostsApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, html, TemplateResult } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
|
||||
@customElement("ak-service-connection-wizard")
|
||||
export class ServiceConnectionWizard extends AKElement {
|
||||
static styles: CSSResult[] = [PFButton];
|
||||
|
||||
@property()
|
||||
createText = msg("Create");
|
||||
|
||||
@property({ attribute: false })
|
||||
connectionTypes: TypeCreate[] = [];
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
firstUpdated(): void {
|
||||
new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllTypesList().then((types) => {
|
||||
this.connectionTypes = types;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<ak-wizard
|
||||
.steps=${["initial"]}
|
||||
header=${msg("New outpost integration")}
|
||||
description=${msg("Create a new outpost integration.")}
|
||||
>
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.connectionTypes}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
this.wizard.steps = [
|
||||
"initial",
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
];
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.connectionTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
slot=${`type-${type.component}-${type.modelName}`}
|
||||
label=${msg(str`Create ${type.name}`)}
|
||||
>
|
||||
${StrictUnsafe(type.component)}
|
||||
</ak-wizard-page-form>
|
||||
`;
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
|
||||
</ak-wizard>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-service-connection-wizard": ServiceConnectionWizard;
|
||||
}
|
||||
}
|
||||
32
web/src/admin/outposts/ak-service-connection-wizard.ts
Normal file
32
web/src/admin/outposts/ak-service-connection-wizard.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import "#admin/outposts/ServiceConnectionDockerForm";
|
||||
import "#admin/outposts/ServiceConnectionKubernetesForm";
|
||||
import "#elements/wizard/FormWizardPage";
|
||||
import "#elements/wizard/TypeCreateWizardPage";
|
||||
import "#elements/wizard/Wizard";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { CreateWizard } from "#elements/wizard/CreateWizard";
|
||||
|
||||
import { OutpostsApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize/init/install";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
|
||||
@customElement("ak-service-connection-wizard")
|
||||
export class AKServiceConnectionWizard extends CreateWizard {
|
||||
public static override verboseName = msg("Outpost Integration");
|
||||
public static override verboseNamePlural = msg("Outpost Integrations");
|
||||
|
||||
#api = new OutpostsApi(DEFAULT_CONFIG);
|
||||
|
||||
protected apiEndpoint = (): Promise<TypeCreate[]> => {
|
||||
return this.#api.outpostsServiceConnectionsAllTypesList();
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-service-connection-wizard": AKServiceConnectionWizard;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import { ModelForm } from "#elements/forms/ModelForm";
|
||||
import { msg } from "@lit/localize";
|
||||
|
||||
export abstract class BasePolicyForm<T extends object> extends ModelForm<T, string> {
|
||||
public static override verboseName = msg("Policy");
|
||||
public static override verboseNamePlural = msg("Policies");
|
||||
|
||||
getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated policy.")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "#admin/groups/ak-group-form";
|
||||
import "#admin/policies/PolicyBindingForm";
|
||||
import "#admin/policies/PolicyWizard";
|
||||
import "#admin/policies/ak-policy-wizard";
|
||||
import "#admin/rbac/ObjectPermissionModal";
|
||||
import "#admin/users/UserForm";
|
||||
import "#components/ak-status-label";
|
||||
@@ -9,14 +9,16 @@ import "#elements/forms/DeleteBulkForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { PFSize } from "#common/enums";
|
||||
import { PolicyBindingCheckTarget, PolicyBindingCheckTargetToLabel } from "#common/policies/utils";
|
||||
|
||||
import { CustomFormElementTagName } from "#elements/forms/unsafe";
|
||||
import { asInstanceInvokerByTagName, modalInvoker } from "#elements/dialogs";
|
||||
import { IconPermissionButton } from "#elements/dialogs/components/IconPermissionButton";
|
||||
import { PaginatedResponse, Table, TableColumn } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
|
||||
import { GroupForm } from "#admin/groups/ak-group-form";
|
||||
import { PolicyWizard } from "#admin/policies/ak-policy-wizard";
|
||||
import { PolicyBindingForm, PolicyBindingNotice } from "#admin/policies/PolicyBindingForm";
|
||||
import { policyEngineModes } from "#admin/policies/PolicyEngineModes";
|
||||
import { UserForm } from "#admin/users/UserForm";
|
||||
@@ -24,36 +26,12 @@ import { UserForm } from "#admin/users/UserForm";
|
||||
import { ModelEnum, PoliciesApi, PolicyBinding } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { css, CSSResult, html, nothing, TemplateResult } from "lit";
|
||||
import { css, CSSResult, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@customElement("ak-bound-policies-list")
|
||||
export class BoundPoliciesList<T extends PolicyBinding = PolicyBinding> extends Table<T> {
|
||||
@property()
|
||||
target?: string;
|
||||
|
||||
@property()
|
||||
policyEngineMode: string = "";
|
||||
|
||||
@property({ type: Array })
|
||||
allowedTypes: PolicyBindingCheckTarget[] = [
|
||||
PolicyBindingCheckTarget.Policy,
|
||||
PolicyBindingCheckTarget.Group,
|
||||
PolicyBindingCheckTarget.User,
|
||||
];
|
||||
|
||||
@property({ type: Array })
|
||||
typeNotices: PolicyBindingNotice[] = [];
|
||||
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
|
||||
order = "order";
|
||||
|
||||
protected bindingEditForm = "ak-policy-binding-form";
|
||||
|
||||
static styles: CSSResult[] = [
|
||||
public static styles: CSSResult[] = [
|
||||
...super.styles,
|
||||
css`
|
||||
/* Align policy engine description to left padding of the card title */
|
||||
@@ -63,11 +41,34 @@ export class BoundPoliciesList<T extends PolicyBinding = PolicyBinding> extends
|
||||
`,
|
||||
];
|
||||
|
||||
@property({ type: String })
|
||||
public target: string | null = null;
|
||||
|
||||
@property({ type: String })
|
||||
public policyEngineMode: string = "";
|
||||
|
||||
@property({ type: Array })
|
||||
public allowedTypes: PolicyBindingCheckTarget[] = [
|
||||
PolicyBindingCheckTarget.Policy,
|
||||
PolicyBindingCheckTarget.Group,
|
||||
PolicyBindingCheckTarget.User,
|
||||
];
|
||||
|
||||
@property({ type: Array })
|
||||
public typeNotices: PolicyBindingNotice[] = [];
|
||||
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
|
||||
public override order = "order";
|
||||
|
||||
protected bindingEditForm = "ak-policy-binding-form";
|
||||
|
||||
get allowedTypesLabel(): string {
|
||||
return this.allowedTypes.map((ct) => PolicyBindingCheckTargetToLabel(ct)).join(" / ");
|
||||
}
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<T>> {
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<T>> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
target: this.target || "",
|
||||
@@ -78,7 +79,7 @@ export class BoundPoliciesList<T extends PolicyBinding = PolicyBinding> extends
|
||||
return item.order?.toString() ?? null;
|
||||
}
|
||||
|
||||
protected columns: TableColumn[] = [
|
||||
protected override columns: TableColumn[] = [
|
||||
[msg("Order"), "order"],
|
||||
[this.allowedTypesLabel],
|
||||
[msg("Enabled"), "enabled"],
|
||||
@@ -86,9 +87,9 @@ export class BoundPoliciesList<T extends PolicyBinding = PolicyBinding> extends
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
getPolicyUserGroupRowLabel(item: PolicyBinding): string {
|
||||
protected getPolicyUserGroupRowLabel(item: PolicyBinding): string {
|
||||
if (item.policy) {
|
||||
return msg(str`Policy ${item.policyObj?.name}`);
|
||||
return msg(str`Policy ${item.policyObj.name}`);
|
||||
} else if (item.group) {
|
||||
return msg(str`Group ${item.groupObj?.name}`);
|
||||
} else if (item.user) {
|
||||
@@ -97,7 +98,7 @@ export class BoundPoliciesList<T extends PolicyBinding = PolicyBinding> extends
|
||||
return msg("-");
|
||||
}
|
||||
|
||||
getPolicyUserGroupRow(item: PolicyBinding): TemplateResult {
|
||||
protected getPolicyUserGroupRow(item: PolicyBinding): SlottedTemplateResult {
|
||||
const label = this.getPolicyUserGroupRowLabel(item);
|
||||
if (item.user) {
|
||||
return html` <a href=${`#/identity/users/${item.user}`}> ${label} </a> `;
|
||||
@@ -108,159 +109,158 @@ export class BoundPoliciesList<T extends PolicyBinding = PolicyBinding> extends
|
||||
return html`${label}`;
|
||||
}
|
||||
|
||||
getObjectEditButton(item: PolicyBinding): SlottedTemplateResult {
|
||||
protected getObjectEditButton(item: PolicyBinding): SlottedTemplateResult {
|
||||
if (item.policyObj) {
|
||||
return html`<ak-forms-modal>
|
||||
${StrictUnsafe<CustomFormElementTagName>(item.policyObj.component, {
|
||||
slot: "form",
|
||||
instancePk: item.policyObj.pk,
|
||||
submitLabel: msg("Save Changes"),
|
||||
headline: msg(str`Update ${item.policyObj.name}`, {
|
||||
id: "form.headline.update",
|
||||
}),
|
||||
})}
|
||||
return html`<button
|
||||
type="button"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
${asInstanceInvokerByTagName(item.policyObj.component, item.policyObj.pk)}
|
||||
>
|
||||
${msg("Edit Policy")}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${msg("Edit Policy")}
|
||||
</button>
|
||||
</ak-forms-modal>`;
|
||||
} else if (item.groupObj) {
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Group")}</span>
|
||||
<ak-group-form slot="form" .instancePk=${item.groupObj.pk}> </ak-group-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${msg("Edit Group")}
|
||||
</button>
|
||||
</ak-forms-modal>`;
|
||||
} else if (item.userObj) {
|
||||
if (item.groupObj) {
|
||||
return html`<button
|
||||
class="pf-c-button pf-m-secondary"
|
||||
${UserForm.asEditModalInvoker(item.userObj.pk)}
|
||||
${GroupForm.asInstanceInvoker(item.groupObj?.pk)}
|
||||
>
|
||||
${msg("Edit Group")}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
if (item.userObj) {
|
||||
return html`<button
|
||||
class="pf-c-button pf-m-secondary"
|
||||
${UserForm.asInstanceInvoker(item.userObj?.pk)}
|
||||
>
|
||||
${msg("Edit User")}
|
||||
</button>`;
|
||||
}
|
||||
return nothing;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Policy binding(s)")}
|
||||
.objects=${this.selectedElements}
|
||||
.metadata=${(item: PolicyBinding) => {
|
||||
return [
|
||||
{ key: msg("Order"), value: item.order.toString() },
|
||||
{
|
||||
key: this.allowedTypesLabel,
|
||||
value: this.getPolicyUserGroupRowLabel(item),
|
||||
},
|
||||
];
|
||||
}}
|
||||
.usedBy=${(item: PolicyBinding) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
|
||||
policyBindingUuid: item.pk,
|
||||
});
|
||||
}}
|
||||
.delete=${(item: PolicyBinding) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({
|
||||
policyBindingUuid: item.pk,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${msg("Delete")}
|
||||
</button>
|
||||
</ak-forms-delete-bulk>`;
|
||||
return html`<ak-spinner-button .callAction=${this.refreshListener} class="pf-m-secondary">
|
||||
${msg("Refresh")}</ak-spinner-button
|
||||
><ak-forms-delete-bulk
|
||||
object-label=${msg("Policy binding(s)")}
|
||||
.objects=${this.selectedElements}
|
||||
.metadata=${(item: PolicyBinding) => {
|
||||
return [
|
||||
{ key: msg("Order"), value: item.order.toString() },
|
||||
{
|
||||
key: this.allowedTypesLabel,
|
||||
value: this.getPolicyUserGroupRowLabel(item),
|
||||
},
|
||||
];
|
||||
}}
|
||||
.usedBy=${(item: PolicyBinding) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
|
||||
policyBindingUuid: item.pk,
|
||||
});
|
||||
}}
|
||||
.delete=${(item: PolicyBinding) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({
|
||||
policyBindingUuid: item.pk,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${msg("Delete")}
|
||||
</button>
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: PolicyBinding): SlottedTemplateResult[] {
|
||||
protected renderNewPolicyButton(): SlottedTemplateResult {
|
||||
return html`<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="button"
|
||||
aria-description="${msg("Open the wizard to create a new policy.")}"
|
||||
${modalInvoker(PolicyWizard, {
|
||||
showBindingPage: true,
|
||||
bindingTarget: this.target,
|
||||
})}
|
||||
>
|
||||
${msg("Create and bind Policy")}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
protected override row(item: PolicyBinding): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`<pre>${item.order}</pre>`,
|
||||
html`${this.getPolicyUserGroupRow(item)}`,
|
||||
html`<ak-status-label type="warning" ?good=${item.enabled}></ak-status-label>`,
|
||||
html`${item.timeout}`,
|
||||
html` ${this.getObjectEditButton(item)}
|
||||
<ak-forms-modal size=${PFSize.Medium}>
|
||||
<span slot="submit">${msg("Save Changes")}</span>
|
||||
<span slot="header">${msg("Update Binding")}</span>
|
||||
${StrictUnsafe<PolicyBindingForm>(this.bindingEditForm, {
|
||||
slot: "form",
|
||||
instancePk: item.pk,
|
||||
allowedTypes: this.allowedTypes,
|
||||
typeNotices: this.typeNotices,
|
||||
targetPk: this.target || "",
|
||||
|
||||
submitLabel: msg("Save Changes"),
|
||||
headline: msg("Update Binding"),
|
||||
html`<div class="ak-c-table__actions">
|
||||
${this.getObjectEditButton(item)}
|
||||
<button
|
||||
type="button"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
${modalInvoker(() => {
|
||||
return StrictUnsafe<PolicyBindingForm>(this.bindingEditForm, {
|
||||
instancePk: item.pk,
|
||||
allowedTypes: this.allowedTypes,
|
||||
typeNotices: this.typeNotices,
|
||||
targetPk: this.target || "",
|
||||
});
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${msg("Edit Binding")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-rbac-object-permission-modal
|
||||
model=${ModelEnum.AuthentikPoliciesPolicybinding}
|
||||
objectPk=${item.pk}
|
||||
>
|
||||
</ak-rbac-object-permission-modal>`,
|
||||
${msg("Edit Binding")}
|
||||
</button>
|
||||
${IconPermissionButton(this.getPolicyUserGroupRowLabel(item), {
|
||||
model: ModelEnum.AuthentikPoliciesPolicybinding,
|
||||
objectPk: item.pk,
|
||||
})}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
protected override renderEmpty(): SlottedTemplateResult {
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon="pf-icon-module"
|
||||
><span>${msg("No Policies bound.")}</span>
|
||||
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
|
||||
<fieldset class="pf-c-form__group pf-m-action" slot="primary">
|
||||
<legend class="sr-only">${msg("Policy actions")}</legend>
|
||||
<ak-policy-wizard
|
||||
createText=${msg("Create and bind Policy")}
|
||||
showBindingPage
|
||||
bindingTarget=${ifDefined(this.target)}
|
||||
></ak-policy-wizard>
|
||||
<ak-forms-modal size=${PFSize.Medium}>
|
||||
${StrictUnsafe<PolicyBindingForm>(this.bindingEditForm, {
|
||||
slot: "form",
|
||||
allowedTypes: this.allowedTypes,
|
||||
typeNotices: this.typeNotices,
|
||||
targetPk: this.target || "",
|
||||
|
||||
submitLabel: msg("Create"),
|
||||
headline: msg("Create Binding"),
|
||||
${this.renderNewPolicyButton()}
|
||||
<button
|
||||
type="button"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
${modalInvoker(() => {
|
||||
return StrictUnsafe<PolicyBindingForm>(this.bindingEditForm, {
|
||||
allowedTypes: this.allowedTypes,
|
||||
typeNotices: this.typeNotices,
|
||||
targetPk: this.target || "",
|
||||
});
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${msg("Bind existing policy/group/user")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
>
|
||||
${msg("Bind existing policy/group/user")}
|
||||
</button>
|
||||
</fieldset>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
renderToolbar(): SlottedTemplateResult {
|
||||
return html`${this.allowedTypes.includes(PolicyBindingCheckTarget.Policy)
|
||||
? html`<ak-policy-wizard
|
||||
createText=${msg("Create and bind Policy")}
|
||||
showBindingPage
|
||||
bindingTarget=${ifDefined(this.target)}
|
||||
></ak-policy-wizard>`
|
||||
: nothing}
|
||||
<ak-forms-modal size=${PFSize.Medium}>
|
||||
${StrictUnsafe<PolicyBindingForm>(this.bindingEditForm, {
|
||||
slot: "form",
|
||||
allowedTypes: this.allowedTypes,
|
||||
typeNotices: this.typeNotices,
|
||||
targetPk: this.target || "",
|
||||
|
||||
submitLabel: msg("Create"),
|
||||
headline: msg("Create Binding"),
|
||||
? this.renderNewPolicyButton()
|
||||
: null}
|
||||
<button
|
||||
type="button"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
${modalInvoker(() => {
|
||||
return StrictUnsafe<PolicyBindingForm>(this.bindingEditForm, {
|
||||
allowedTypes: this.allowedTypes,
|
||||
typeNotices: this.typeNotices,
|
||||
targetPk: this.target || "",
|
||||
});
|
||||
})}
|
||||
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${msg(str`Bind existing ${this.allowedTypesLabel}`)}
|
||||
</button>
|
||||
</ak-forms-modal> `;
|
||||
>
|
||||
${msg(str`Bind existing ${this.allowedTypesLabel}`)}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
renderPolicyEngineMode() {
|
||||
|
||||
@@ -39,7 +39,9 @@ export class PolicyBindingForm<T extends PolicyBinding = PolicyBinding> extends
|
||||
T,
|
||||
string
|
||||
> {
|
||||
static styles: CSSResult[] = [...super.styles, PFContent];
|
||||
public static styles: CSSResult[] = [...super.styles, PFContent];
|
||||
public static verboseName = msg("Policy Binding");
|
||||
public static verboseNamePlural = msg("Policy Bindings");
|
||||
|
||||
async loadInstance(pk: string): Promise<T> {
|
||||
const binding = await new PoliciesApi(DEFAULT_CONFIG).policiesBindingsRetrieve({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "#admin/policies/PolicyTestForm";
|
||||
import "#admin/policies/PolicyWizard";
|
||||
import "#admin/policies/ak-policy-wizard";
|
||||
import "#admin/policies/dummy/DummyPolicyForm";
|
||||
import "#admin/policies/event_matcher/EventMatcherPolicyForm";
|
||||
import "#admin/policies/expiry/ExpiryPolicyForm";
|
||||
@@ -15,33 +15,37 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { CustomFormElementTagName } from "#elements/forms/unsafe";
|
||||
import { IconEditButtonByTagName, modalInvoker } from "#elements/dialogs";
|
||||
import { IconPermissionButton } from "#elements/dialogs/components/IconPermissionButton";
|
||||
import { PFColor } from "#elements/Label";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
|
||||
import { PoliciesApi, Policy } from "@goauthentik/api";
|
||||
import { PolicyWizard } from "#admin/policies/ak-policy-wizard";
|
||||
import { PolicyTestForm } from "#admin/policies/PolicyTestForm";
|
||||
|
||||
import { ModelEnum, PoliciesApi, Policy } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-policy-list")
|
||||
export class PolicyListPage extends TablePage<Policy> {
|
||||
protected override searchEnabled = true;
|
||||
public pageTitle = msg("Policies");
|
||||
public pageDescription = msg(
|
||||
|
||||
public override pageTitle = msg("Policies");
|
||||
public override pageDescription = msg(
|
||||
"Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages.",
|
||||
);
|
||||
public pageIcon = "pf-icon pf-icon-infrastructure";
|
||||
public override pageIcon = "pf-icon pf-icon-infrastructure";
|
||||
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
public override searchPlaceholder = msg("Search for a policy by name or type...");
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
public override order = "name";
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<Policy>> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesAllList(await this.defaultEndpointConfig());
|
||||
@@ -54,7 +58,7 @@ export class PolicyListPage extends TablePage<Policy> {
|
||||
[msg("Actions")],
|
||||
];
|
||||
|
||||
row(item: Policy): SlottedTemplateResult[] {
|
||||
protected override row(item: Policy): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`<div>${item.name}</div>
|
||||
${(item.boundTo || 0) > 0
|
||||
@@ -65,38 +69,32 @@ export class PolicyListPage extends TablePage<Policy> {
|
||||
${msg("Warning: Policy is not assigned.")}
|
||||
</ak-label>`}`,
|
||||
html`${item.verboseName}`,
|
||||
html`<ak-forms-modal>
|
||||
${StrictUnsafe<CustomFormElementTagName>(item.component, {
|
||||
slot: "form",
|
||||
instancePk: item.pk,
|
||||
submitLabel: msg("Save Changes"),
|
||||
headline: msg(str`Update ${item.verboseName}`, {
|
||||
id: "form.headline.update",
|
||||
}),
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButtonByTagName(item.component, item.pk)}
|
||||
${IconPermissionButton(item.name, {
|
||||
model: item.metaModelName as ModelEnum,
|
||||
objectPk: item.pk,
|
||||
})}
|
||||
|
||||
<ak-rbac-object-permission-modal model=${item.metaModelName} objectPk=${item.pk}>
|
||||
</ak-rbac-object-permission-modal>
|
||||
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
|
||||
<span slot="submit">${msg("Test")}</span>
|
||||
<span slot="header">${msg("Test Policy")}</span>
|
||||
<ak-policy-test-form slot="form" .policy=${item}> </ak-policy-test-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Test")}>
|
||||
<i class="fas fa-vial" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>`,
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
${modalInvoker(
|
||||
PolicyTestForm,
|
||||
{ policy: item },
|
||||
{
|
||||
closedBy: "closerequest",
|
||||
},
|
||||
)}
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Test")}>
|
||||
<i class="fas fa-vial" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Policy / Policies")}
|
||||
@@ -118,12 +116,21 @@ export class PolicyListPage extends TablePage<Policy> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`<ak-policy-wizard> </ak-policy-wizard>`;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return html`
|
||||
<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="button"
|
||||
aria-description="${msg("Open the wizard to create a new policy.")}"
|
||||
${PolicyWizard.asModalInvoker()}
|
||||
>
|
||||
${msg("New Policy")}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html` ${super.renderToolbar()}
|
||||
protected override renderToolbar(): SlottedTemplateResult {
|
||||
return html`${super.renderToolbar()}
|
||||
<ak-forms-confirm
|
||||
successMessage=${msg("Successfully cleared policy cache")}
|
||||
errorMessage=${msg("Failed to delete policy cache")}
|
||||
|
||||
@@ -5,8 +5,12 @@ import "#elements/forms/HorizontalFormElement";
|
||||
import "#elements/forms/SearchSelect/index";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { PFSize } from "#common/enums";
|
||||
|
||||
import { Form } from "#elements/forms/Form";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { AKLabel } from "#components/ak-label";
|
||||
|
||||
import {
|
||||
CoreApi,
|
||||
@@ -21,40 +25,20 @@ import {
|
||||
import YAML from "yaml";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, CSSResult, html, nothing, TemplateResult } from "lit";
|
||||
import { css, CSSResult, html, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
|
||||
@customElement("ak-policy-test-form")
|
||||
export class PolicyTestForm extends Form<PolicyTestRequest> {
|
||||
@property({ attribute: false })
|
||||
public policy?: Policy;
|
||||
public static verboseName = msg("Policy");
|
||||
public static verboseNamePlural = msg("Policies");
|
||||
public static createLabel = msg("Test");
|
||||
|
||||
@state()
|
||||
protected result: PolicyTestResult | null = null;
|
||||
public override cancelable = true;
|
||||
|
||||
@property({ attribute: false })
|
||||
public request?: PolicyTestRequest;
|
||||
|
||||
public override reset(): void {
|
||||
super.reset();
|
||||
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
return msg("Successfully sent test-request.");
|
||||
}
|
||||
|
||||
async send(data: PolicyTestRequest): Promise<PolicyTestResult> {
|
||||
this.request = data;
|
||||
const result = await new PoliciesApi(DEFAULT_CONFIG).policiesAllTestCreate({
|
||||
policyUuid: this.policy?.pk || "",
|
||||
policyTestRequest: data,
|
||||
});
|
||||
return (this.result = result);
|
||||
}
|
||||
public override size = PFSize.XLarge;
|
||||
|
||||
static styles: CSSResult[] = [
|
||||
...super.styles,
|
||||
@@ -66,9 +50,52 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
|
||||
`,
|
||||
];
|
||||
|
||||
renderResult(): TemplateResult {
|
||||
return html`
|
||||
<ak-form-element-horizontal label=${msg("Passing")}>
|
||||
#api = new PoliciesApi(DEFAULT_CONFIG);
|
||||
|
||||
protected override formatSubmitLabel(submitLabel?: string | null): string {
|
||||
return submitLabel || msg("Run Test");
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
public policy: Policy | null = null;
|
||||
|
||||
@state()
|
||||
protected result: PolicyTestResult | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
public request: PolicyTestRequest | null = null;
|
||||
|
||||
public get verboseName(): string | null {
|
||||
return this.policy?.verboseName || null;
|
||||
}
|
||||
|
||||
public get verboseNamePlural(): string | null {
|
||||
return this.policy?.verboseNamePlural || null;
|
||||
}
|
||||
|
||||
public override reset(): void {
|
||||
super.reset();
|
||||
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
public override getSuccessMessage(): string {
|
||||
return msg("Successfully sent test-request.");
|
||||
}
|
||||
|
||||
protected override async send(data: PolicyTestRequest): Promise<PolicyTestResult> {
|
||||
this.request = data;
|
||||
|
||||
this.result = await this.#api.policiesAllTestCreate({
|
||||
policyUuid: this.policy?.pk || "",
|
||||
policyTestRequest: data,
|
||||
});
|
||||
|
||||
return this.result;
|
||||
}
|
||||
|
||||
protected renderResult(): SlottedTemplateResult {
|
||||
return html`<ak-form-element-horizontal label=${msg("Passing")}>
|
||||
<div class="pf-c-form__group-label">
|
||||
<div class="c-form__horizontal-group">
|
||||
<span class="pf-c-form__label-text">
|
||||
@@ -103,13 +130,13 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
`;
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
protected override renderForm(): SlottedTemplateResult {
|
||||
return html`<ak-form-element-horizontal label=${msg("User")} required name="user">
|
||||
<ak-search-select
|
||||
placeholder=${msg("Select a user...")}
|
||||
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
||||
const args: CoreUsersListRequest = {
|
||||
ordering: "username",
|
||||
@@ -135,14 +162,28 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
|
||||
>
|
||||
</ak-search-select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Context")} name="context">
|
||||
<ak-codemirror mode="yaml" value=${YAML.stringify(this.request?.context ?? {})}>
|
||||
|
||||
<ak-form-element-horizontal name="context">
|
||||
${AKLabel(
|
||||
{
|
||||
slot: "label",
|
||||
className: "pf-c-form__group-label",
|
||||
htmlFor: "context",
|
||||
},
|
||||
msg("Context"),
|
||||
)}
|
||||
<ak-codemirror
|
||||
id="context"
|
||||
mode="yaml"
|
||||
value=${YAML.stringify(this.request?.context ?? {})}
|
||||
>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Set custom attributes using YAML or JSON.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${this.result ? this.renderResult() : nothing}`;
|
||||
|
||||
${this.result ? this.renderResult() : null}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import "#admin/policies/dummy/DummyPolicyForm";
|
||||
import "#admin/policies/event_matcher/EventMatcherPolicyForm";
|
||||
import "#admin/policies/expiry/ExpiryPolicyForm";
|
||||
import "#admin/policies/expression/ExpressionPolicyForm";
|
||||
import "#admin/policies/geoip/GeoIPPolicyForm";
|
||||
import "#admin/policies/password/PasswordPolicyForm";
|
||||
import "#admin/policies/reputation/ReputationPolicyForm";
|
||||
import "#admin/policies/unique_password/UniquePasswordPolicyForm";
|
||||
import "#elements/wizard/FormWizardPage";
|
||||
import "#elements/wizard/TypeCreateWizardPage";
|
||||
import "#elements/wizard/Wizard";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
import { FormWizardPage } from "#elements/wizard/FormWizardPage";
|
||||
import type { Wizard } from "#elements/wizard/Wizard";
|
||||
|
||||
import { PolicyBindingForm } from "#admin/policies/PolicyBindingForm";
|
||||
|
||||
import { PoliciesApi, Policy, PolicyBinding, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, html, nothing, TemplateResult } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
|
||||
@customElement("ak-policy-wizard")
|
||||
export class PolicyWizard extends AKElement {
|
||||
static styles: CSSResult[] = [PFButton];
|
||||
|
||||
@property()
|
||||
createText = msg("Create");
|
||||
|
||||
@property({ type: Boolean })
|
||||
showBindingPage = false;
|
||||
|
||||
@property()
|
||||
bindingTarget?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
policyTypes: TypeCreate[] = [];
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
firstUpdated(): void {
|
||||
new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => {
|
||||
this.policyTypes = types;
|
||||
});
|
||||
}
|
||||
|
||||
selectListener = ({ detail }: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
|
||||
const { component, modelName } = detail;
|
||||
const idx = this.wizard.steps.indexOf("initial") + 1;
|
||||
|
||||
// Exclude all current steps starting with type-,
|
||||
// this happens when the user selects a type and then goes back
|
||||
this.wizard.steps = this.wizard.steps.filter((step) => !step.startsWith("type-"));
|
||||
|
||||
this.wizard.steps.splice(idx, 0, `type-${component}-${modelName}`);
|
||||
|
||||
this.wizard.isValid = true;
|
||||
};
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<ak-wizard
|
||||
.steps=${this.showBindingPage ? ["initial", "create-binding"] : ["initial"]}
|
||||
header=${msg("New policy")}
|
||||
description=${msg("Create a new policy.")}
|
||||
>
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.policyTypes}
|
||||
@select=${this.selectListener}
|
||||
>
|
||||
</ak-wizard-page-type-create>
|
||||
|
||||
${this.policyTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
slot=${`type-${type.component}-${type.modelName}`}
|
||||
label=${msg(str`Create ${type.name}`)}
|
||||
>
|
||||
${StrictUnsafe(type.component)}
|
||||
</ak-wizard-page-form>
|
||||
`;
|
||||
})}
|
||||
${this.showBindingPage
|
||||
? html`<ak-wizard-page-form
|
||||
slot="create-binding"
|
||||
label=${msg("Create Binding")}
|
||||
.activePageCallback=${async (context: FormWizardPage) => {
|
||||
const createSlot = context.host.steps[1];
|
||||
const bindingForm =
|
||||
context.querySelector<PolicyBindingForm>(
|
||||
"ak-policy-binding-form",
|
||||
);
|
||||
if (!bindingForm) return;
|
||||
bindingForm.instance = {
|
||||
policy: (context.host.state[createSlot] as Policy).pk,
|
||||
} as PolicyBinding;
|
||||
}}
|
||||
>
|
||||
<ak-policy-binding-form
|
||||
.targetPk=${this.bindingTarget}
|
||||
></ak-policy-binding-form>
|
||||
</ak-wizard-page-form>`
|
||||
: nothing}
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
|
||||
</ak-wizard>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-policy-wizard": PolicyWizard;
|
||||
}
|
||||
}
|
||||
90
web/src/admin/policies/ak-policy-wizard.ts
Normal file
90
web/src/admin/policies/ak-policy-wizard.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import "#admin/policies/dummy/DummyPolicyForm";
|
||||
import "#admin/policies/event_matcher/EventMatcherPolicyForm";
|
||||
import "#admin/policies/expiry/ExpiryPolicyForm";
|
||||
import "#admin/policies/expression/ExpressionPolicyForm";
|
||||
import "#admin/policies/geoip/GeoIPPolicyForm";
|
||||
import "#admin/policies/password/PasswordPolicyForm";
|
||||
import "#admin/policies/reputation/ReputationPolicyForm";
|
||||
import "#admin/policies/unique_password/UniquePasswordPolicyForm";
|
||||
import "#elements/wizard/FormWizardPage";
|
||||
import "#elements/wizard/TypeCreateWizardPage";
|
||||
import "#elements/wizard/Wizard";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { CreateWizard } from "#elements/wizard/CreateWizard";
|
||||
import { FormWizardPage } from "#elements/wizard/FormWizardPage";
|
||||
import { TypeCreateWizardPageLayouts } from "#elements/wizard/TypeCreateWizardPage";
|
||||
|
||||
import { PolicyBindingForm } from "#admin/policies/PolicyBindingForm";
|
||||
|
||||
import { PoliciesApi, Policy, PolicyBinding, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { html, PropertyValues } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-policy-wizard")
|
||||
export class PolicyWizard extends CreateWizard {
|
||||
#api = new PoliciesApi(DEFAULT_CONFIG);
|
||||
|
||||
@property({ type: Boolean })
|
||||
public showBindingPage = false;
|
||||
|
||||
@property()
|
||||
public bindingTarget: string | null = null;
|
||||
|
||||
public override initialSteps = this.showBindingPage
|
||||
? ["initial", "create-binding"]
|
||||
: ["initial"];
|
||||
|
||||
public static override verboseName = msg("Policy");
|
||||
public static override verboseNamePlural = msg("Policies");
|
||||
|
||||
public override layout = TypeCreateWizardPageLayouts.list;
|
||||
|
||||
protected apiEndpoint = async (requestInit?: RequestInit): Promise<TypeCreate[]> => {
|
||||
return this.#api.policiesAllTypesList(requestInit);
|
||||
};
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has("showBindingPage")) {
|
||||
this.initialSteps = this.showBindingPage ? ["initial", "create-binding"] : ["initial"];
|
||||
}
|
||||
}
|
||||
|
||||
protected createBindingActivate = async (page: FormWizardPage) => {
|
||||
const createSlot = page.host.steps[1];
|
||||
const bindingForm = page.querySelector<PolicyBindingForm>("ak-policy-binding-form");
|
||||
|
||||
if (!bindingForm) return;
|
||||
|
||||
bindingForm.instance = {
|
||||
policy: (page.host.state[createSlot] as Policy).pk,
|
||||
} as PolicyBinding;
|
||||
};
|
||||
|
||||
protected renderForms(): SlottedTemplateResult {
|
||||
const bindingPage = this.showBindingPage
|
||||
? html`<ak-wizard-page-form
|
||||
slot="create-binding"
|
||||
headline=${msg("Create Binding")}
|
||||
.activePageCallback=${this.createBindingActivate}
|
||||
>
|
||||
<ak-policy-binding-form .targetPk=${this.bindingTarget}></ak-policy-binding-form>
|
||||
</ak-wizard-page-form>`
|
||||
: null;
|
||||
|
||||
return [super.renderForms(), bindingPage];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-policy-wizard": PolicyWizard;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import "#components/ak-text-input";
|
||||
import "#components/ak-switch-input";
|
||||
import "#elements/forms/FormGroup";
|
||||
import "#elements/forms/HorizontalFormElement";
|
||||
@@ -34,19 +35,21 @@ export class DummyPolicyForm extends BasePolicyForm<DummyPolicy> {
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html` <span>
|
||||
return html`<span>
|
||||
${msg(
|
||||
"A policy used for testing. Always returns the same result as specified below after waiting a random duration.",
|
||||
)}
|
||||
</span>
|
||||
<ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-text-input
|
||||
label=${msg("Policy Name")}
|
||||
required
|
||||
name="name"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
placeholder=${msg("Type a policy name...")}
|
||||
autocomplete="off"
|
||||
autofocus
|
||||
>
|
||||
</ak-text-input>
|
||||
<ak-switch-input
|
||||
name="executionLogging"
|
||||
label=${msg("Execution logging")}
|
||||
|
||||
@@ -15,25 +15,24 @@ import { ModelEnum, PoliciesApi, Reputation } from "@goauthentik/api";
|
||||
import getUnicodeFlagIcon from "country-flag-icons/unicode";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-policy-reputation-list")
|
||||
export class ReputationListPage extends TablePage<Reputation> {
|
||||
protected override searchEnabled = true;
|
||||
public pageTitle = msg("Reputation scores");
|
||||
public pageDescription = msg(
|
||||
|
||||
public override pageTitle = msg("Reputation scores");
|
||||
public override pageDescription = msg(
|
||||
"Reputation for IP and user identifiers. Scores are decreased for each failed login and increased for each successful login.",
|
||||
);
|
||||
public pageIcon = "fa fa-ban";
|
||||
public override pageIcon = "fa fa-ban";
|
||||
public override order = "identifier";
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
public override searchPlaceholder = msg("Search for a reputation by identifier or IP...");
|
||||
|
||||
@property()
|
||||
order = "identifier";
|
||||
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<Reputation>> {
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<Reputation>> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
});
|
||||
@@ -51,7 +50,7 @@ export class ReputationListPage extends TablePage<Reputation> {
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Reputation")}
|
||||
@@ -73,9 +72,9 @@ export class ReputationListPage extends TablePage<Reputation> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: Reputation): SlottedTemplateResult[] {
|
||||
protected override row(item: Reputation): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`${item.identifier}`,
|
||||
item.identifier,
|
||||
html`${item.ipGeoData?.country
|
||||
? html` ${getUnicodeFlagIcon(item.ipGeoData.country)} `
|
||||
: nothing}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "#elements/CodeMirror/ak-codemirror";
|
||||
import "#components/ak-text-input";
|
||||
|
||||
import { docLink } from "#common/global";
|
||||
|
||||
@@ -20,6 +21,9 @@ export abstract class BasePropertyMappingForm<T extends PropertyMapping> extends
|
||||
> {
|
||||
protected docLink: string | URL = "/add-secure-apps/providers/property-mappings/expression";
|
||||
|
||||
public static override verboseName = msg("Property Mapping");
|
||||
public static override verboseNamePlural = msg("Property Mappings");
|
||||
|
||||
getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated mapping.")
|
||||
@@ -31,14 +35,15 @@ export abstract class BasePropertyMappingForm<T extends PropertyMapping> extends
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
return html` <ak-form-element-horizontal label=${msg("Name")} required name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
return html`<ak-text-input
|
||||
label=${msg("Mapping Name")}
|
||||
placeholder=${msg("Type a name for this mapping...")}
|
||||
autocomplete="off"
|
||||
required
|
||||
name="name"
|
||||
value="${ifDefined(this.instance?.name)}"
|
||||
>
|
||||
</ak-text-input>
|
||||
${this.renderExtraFields()}
|
||||
<ak-form-element-horizontal label=${msg("Expression")} required name="expression">
|
||||
<ak-codemirror mode="python" value="${ifDefined(this.instance?.expression)}">
|
||||
|
||||
@@ -14,7 +14,7 @@ import "#admin/property-mappings/PropertyMappingSourceSAMLForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceSCIMForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceTelegramForm";
|
||||
import "#admin/property-mappings/PropertyMappingTestForm";
|
||||
import "#admin/property-mappings/PropertyMappingWizard";
|
||||
import "#admin/property-mappings/ak-property-mapping-wizard";
|
||||
import "#admin/rbac/ObjectPermissionModal";
|
||||
import "#elements/forms/DeleteBulkForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
@@ -22,49 +22,54 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { CustomFormElementTagName } from "#elements/forms/unsafe";
|
||||
import { IconEditButtonByTagName, modalInvoker } from "#elements/dialogs";
|
||||
import { IconPermissionButton } from "#elements/dialogs/components/IconPermissionButton";
|
||||
import { getURLParam, updateURLParams } from "#elements/router/RouteMatch";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
|
||||
import { PropertyMapping, PropertymappingsApi } from "@goauthentik/api";
|
||||
import { AKPropertyMappingWizard } from "#admin/property-mappings/ak-property-mapping-wizard";
|
||||
import { PropertyMappingTestForm } from "#admin/property-mappings/PropertyMappingTestForm";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { ModelEnum, PropertyMapping, PropertymappingsApi } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-property-mapping-list")
|
||||
export class PropertyMappingListPage extends TablePage<PropertyMapping> {
|
||||
protected override searchEnabled = true;
|
||||
public pageTitle = msg("Property Mappings");
|
||||
public pageDescription = msg("Control how authentik exposes and interprets information.");
|
||||
public pageIcon = "pf-icon pf-icon-blueprint";
|
||||
public override pageTitle = msg("Property Mappings");
|
||||
public override pageDescription = msg(
|
||||
"Control how authentik exposes and interprets information.",
|
||||
);
|
||||
public override pageIcon = "pf-icon pf-icon-blueprint";
|
||||
public override searchPlaceholder = msg("Search for a property mapping by name or type...");
|
||||
|
||||
checkbox = true;
|
||||
clearOnRefresh = true;
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
public override order = "name";
|
||||
|
||||
@state()
|
||||
hideManaged = getURLParam<boolean>("hideManaged", true);
|
||||
protected hideManaged = getURLParam<boolean>("hideManaged", true);
|
||||
|
||||
async apiEndpoint(): Promise<PaginatedResponse<PropertyMapping>> {
|
||||
protected override async apiEndpoint(): Promise<PaginatedResponse<PropertyMapping>> {
|
||||
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
managedIsnull: this.hideManaged ? true : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
protected columns: TableColumn[] = [
|
||||
protected override columns: TableColumn[] = [
|
||||
[msg("Name"), "name"],
|
||||
[msg("Type"), "type"],
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
protected override renderToolbarSelected(): SlottedTemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
object-label=${msg("Property Mapping(s)")}
|
||||
@@ -86,46 +91,45 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: PropertyMapping): SlottedTemplateResult[] {
|
||||
protected override row(item: PropertyMapping): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`${item.name}`,
|
||||
html`${item.verboseName}`,
|
||||
html` <ak-forms-modal>
|
||||
${StrictUnsafe<CustomFormElementTagName>(item.component, {
|
||||
slot: "form",
|
||||
instancePk: item.pk,
|
||||
submitLabel: msg("Save Changes"),
|
||||
headline: msg(str`Update ${item.verboseName}`, {
|
||||
id: "form.headline.update",
|
||||
}),
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-rbac-object-permission-modal model=${item.metaModelName} objectPk=${item.pk}>
|
||||
</ak-rbac-object-permission-modal>
|
||||
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
|
||||
<span slot="submit">${msg("Test")}</span>
|
||||
<span slot="header">${msg("Test Property Mapping")}</span>
|
||||
<ak-property-mapping-test-form slot="form" .mapping=${item}>
|
||||
</ak-property-mapping-test-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<pf-tooltip position="top" content=${msg("Test")}>
|
||||
<i class="fas fa-vial" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>`,
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButtonByTagName(item.component, item.pk)}
|
||||
${IconPermissionButton(item.name, {
|
||||
model: item.metaModelName as ModelEnum,
|
||||
objectPk: item.pk,
|
||||
})}
|
||||
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
${modalInvoker(
|
||||
PropertyMappingTestForm,
|
||||
{ mapping: item },
|
||||
{
|
||||
closedBy: "closerequest",
|
||||
},
|
||||
)}
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Test")}>
|
||||
<i class="fas fa-vial" aria-hidden="true"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`<ak-property-mapping-wizard></ak-property-mapping-wizard> `;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return html`<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
${modalInvoker(AKPropertyMappingWizard)}
|
||||
>
|
||||
${msg("New Property Mapping")}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
renderToolbarAfter(): TemplateResult {
|
||||
protected override renderToolbarAfter(): SlottedTemplateResult {
|
||||
return html`<div class="pf-c-toolbar__group pf-m-filter-group">
|
||||
<div class="pf-c-toolbar__item pf-m-search-filter">
|
||||
<div class="pf-c-input-group">
|
||||
|
||||
@@ -3,8 +3,12 @@ import "#elements/forms/HorizontalFormElement";
|
||||
import "#elements/forms/SearchSelect/index";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { PFSize } from "#common/enums";
|
||||
|
||||
import { Form } from "#elements/forms/Form";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { AKLabel } from "#components/ak-label";
|
||||
|
||||
import {
|
||||
CoreApi,
|
||||
@@ -22,45 +26,80 @@ import {
|
||||
import YAML from "yaml";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, nothing, TemplateResult } from "lit";
|
||||
import { html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@customElement("ak-property-mapping-test-form")
|
||||
export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
|
||||
@property({ attribute: false })
|
||||
mapping?: PropertyMapping;
|
||||
export class PropertyMappingTestForm extends Form<PropertyMappingTestRequest> {
|
||||
public static verboseName = msg("Property Mapping");
|
||||
public static verboseNamePlural = msg("Property Mappings");
|
||||
public static createLabel = msg("Test");
|
||||
|
||||
public override cancelable = true;
|
||||
public override size = PFSize.XLarge;
|
||||
|
||||
#api = new PropertymappingsApi(DEFAULT_CONFIG);
|
||||
|
||||
protected override formatSubmitLabel(submitLabel?: string | null): string {
|
||||
return submitLabel || msg("Run Test");
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
result?: PropertyMappingTestResult;
|
||||
public mapping: PropertyMapping | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
request?: PropertyMappingTestRequest;
|
||||
public result: PropertyMappingTestResult | null = null;
|
||||
|
||||
getSuccessMessage(): string {
|
||||
@property({ attribute: false })
|
||||
public request: PropertyMappingTestRequest | null = null;
|
||||
|
||||
public override getSuccessMessage(): string {
|
||||
return msg("Successfully sent test-request.");
|
||||
}
|
||||
|
||||
async send(data: PropertyMappingTestRequest): Promise<PropertyMappingTestResult> {
|
||||
protected override async send(
|
||||
data: PropertyMappingTestRequest,
|
||||
): Promise<PropertyMappingTestResult> {
|
||||
this.request = data;
|
||||
const result = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTestCreate({
|
||||
|
||||
this.result = await this.#api.propertymappingsAllTestCreate({
|
||||
pmUuid: this.mapping?.pk || "",
|
||||
propertyMappingTestRequest: data,
|
||||
formatResult: true,
|
||||
});
|
||||
return (this.result = result);
|
||||
|
||||
return this.result;
|
||||
}
|
||||
|
||||
renderResult(): TemplateResult {
|
||||
return html`<ak-form-element-horizontal label=${msg("Result")}>
|
||||
public get verboseName(): string | null {
|
||||
return this.mapping?.verboseName || null;
|
||||
}
|
||||
|
||||
public get verboseNamePlural(): string | null {
|
||||
return this.mapping?.verboseNamePlural || null;
|
||||
}
|
||||
|
||||
protected renderResult(): SlottedTemplateResult {
|
||||
return html`<ak-form-element-horizontal>
|
||||
${this.result?.successful
|
||||
? html`<ak-codemirror
|
||||
mode="javascript"
|
||||
readonly
|
||||
value="${ifDefined(this.result?.result)}"
|
||||
>
|
||||
</ak-codemirror>`
|
||||
: html` <div class="pf-c-form__group-label">
|
||||
? html`${AKLabel(
|
||||
{
|
||||
slot: "label",
|
||||
className: "pf-c-form__group-label",
|
||||
htmlFor: "result",
|
||||
},
|
||||
msg("Result"),
|
||||
)}
|
||||
|
||||
<ak-codemirror
|
||||
id="result"
|
||||
mode="javascript"
|
||||
readonly
|
||||
value="${ifDefined(this.result?.result)}"
|
||||
>
|
||||
</ak-codemirror>`
|
||||
: html`<div class="pf-c-form__group-label">
|
||||
<div class="c-form__horizontal-group">
|
||||
<span class="pf-c-form__label-text">
|
||||
<pre>${this.result?.result}</pre>
|
||||
@@ -70,15 +109,26 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
renderExampleButtons() {
|
||||
return this.mapping?.metaModelName ===
|
||||
ModelEnum.AuthentikSourcesLdapLdapsourcepropertymapping
|
||||
? html`<p>${msg("Example context data")}</p>
|
||||
${this.renderExampleLDAP()}`
|
||||
: nothing;
|
||||
protected renderExampleButtons(): SlottedTemplateResult {
|
||||
if (
|
||||
this.mapping?.metaModelName !== ModelEnum.AuthentikSourcesLdapLdapsourcepropertymapping
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return html`<div class="pf-c-form__group">
|
||||
${AKLabel(
|
||||
{
|
||||
slot: "label",
|
||||
className: "pf-c-form__group-label",
|
||||
},
|
||||
msg("Example Context Data"),
|
||||
)}
|
||||
<p class="pf-c-form__helper-text">${this.renderExampleLDAP()}</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderExampleLDAP(): TemplateResult {
|
||||
protected renderExampleLDAP(): SlottedTemplateResult {
|
||||
return html`
|
||||
<button
|
||||
type="button"
|
||||
@@ -127,9 +177,10 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
|
||||
`;
|
||||
}
|
||||
|
||||
protected override renderForm(): TemplateResult {
|
||||
protected override renderForm(): SlottedTemplateResult {
|
||||
return html`<ak-form-element-horizontal label=${msg("User")} name="user">
|
||||
<ak-search-select
|
||||
placeholder=${msg("Select a user...")}
|
||||
blankable
|
||||
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
||||
const args: CoreUsersListRequest = {
|
||||
@@ -144,7 +195,7 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
|
||||
.renderElement=${(user: User): string => {
|
||||
return user.username;
|
||||
}}
|
||||
.renderDescription=${(user: User): TemplateResult => {
|
||||
.renderDescription=${(user: User): SlottedTemplateResult => {
|
||||
return html`${user.name}`;
|
||||
}}
|
||||
.value=${(user: User | undefined): number | undefined => {
|
||||
@@ -158,6 +209,7 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Group")} name="group">
|
||||
<ak-search-select
|
||||
placeholder=${msg("Select a group...")}
|
||||
blankable
|
||||
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
||||
const args: CoreGroupsListRequest = {
|
||||
@@ -181,17 +233,30 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
|
||||
>
|
||||
</ak-search-select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Context")} name="context">
|
||||
<ak-codemirror mode="yaml" value=${YAML.stringify(this.request?.context ?? {})}>
|
||||
${this.renderExampleButtons()}
|
||||
|
||||
<ak-form-element-horizontal name="context">
|
||||
${AKLabel(
|
||||
{
|
||||
slot: "label",
|
||||
className: "pf-c-form__group-label",
|
||||
htmlFor: "context",
|
||||
},
|
||||
msg("Context"),
|
||||
)}
|
||||
<ak-codemirror
|
||||
id="context"
|
||||
mode="yaml"
|
||||
value=${YAML.stringify(this.request?.context ?? {})}
|
||||
>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">${this.renderExampleButtons()}</p>
|
||||
</ak-form-element-horizontal>
|
||||
${this.result ? this.renderResult() : nothing}`;
|
||||
${this.result ? this.renderResult() : null}`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-property-mapping-test-form": PolicyTestForm;
|
||||
"ak-property-mapping-test-form": PropertyMappingTestForm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import "#admin/property-mappings/PropertyMappingNotification";
|
||||
import "#admin/property-mappings/PropertyMappingProviderGoogleWorkspaceForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderMicrosoftEntraForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderRACForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderRadiusForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderSAMLForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderSCIMForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderScopeForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceKerberosForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceLDAPForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceOAuthForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourcePlexForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceSAMLForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceSCIMForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceTelegramForm";
|
||||
import "#admin/property-mappings/PropertyMappingTestForm";
|
||||
import "#elements/wizard/FormWizardPage";
|
||||
import "#elements/wizard/TypeCreateWizardPage";
|
||||
import "#elements/wizard/Wizard";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
import type { Wizard } from "#elements/wizard/Wizard";
|
||||
|
||||
import { PropertymappingsApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
|
||||
@customElement("ak-property-mapping-wizard")
|
||||
export class PropertyMappingWizard extends AKElement {
|
||||
static styles = [PFButton];
|
||||
|
||||
@property({ attribute: false })
|
||||
mappingTypes: TypeCreate[] = [];
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.mappingTypes = await new PropertymappingsApi(
|
||||
DEFAULT_CONFIG,
|
||||
).propertymappingsAllTypesList();
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<ak-wizard
|
||||
.steps=${["initial"]}
|
||||
header=${msg("New property mapping")}
|
||||
description=${msg("Create a new property mapping.")}
|
||||
>
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.mappingTypes}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
this.wizard.steps = [
|
||||
"initial",
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
];
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.mappingTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
slot=${`type-${type.component}-${type.modelName}`}
|
||||
label=${msg(str`Create ${type.name}`)}
|
||||
>
|
||||
${StrictUnsafe(type.component)}
|
||||
</ak-wizard-page-form>
|
||||
`;
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||
</ak-wizard>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-property-mapping-wizard": PropertyMappingWizard;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import "#admin/property-mappings/PropertyMappingNotification";
|
||||
import "#admin/property-mappings/PropertyMappingProviderGoogleWorkspaceForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderMicrosoftEntraForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderRACForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderRadiusForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderSAMLForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderSCIMForm";
|
||||
import "#admin/property-mappings/PropertyMappingProviderScopeForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceKerberosForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceLDAPForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceOAuthForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourcePlexForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceSAMLForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceSCIMForm";
|
||||
import "#admin/property-mappings/PropertyMappingSourceTelegramForm";
|
||||
import "#admin/property-mappings/PropertyMappingTestForm";
|
||||
import "#elements/wizard/FormWizardPage";
|
||||
import "#elements/wizard/TypeCreateWizardPage";
|
||||
import "#elements/wizard/Wizard";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { CreateWizard } from "#elements/wizard/CreateWizard";
|
||||
|
||||
import { PropertymappingsApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize/init/install";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
|
||||
@customElement("ak-property-mapping-wizard")
|
||||
export class AKPropertyMappingWizard extends CreateWizard {
|
||||
#api = new PropertymappingsApi(DEFAULT_CONFIG);
|
||||
|
||||
protected override apiEndpoint(requestInit?: RequestInit): Promise<TypeCreate[]> {
|
||||
return this.#api.propertymappingsAllTypesList(requestInit);
|
||||
}
|
||||
|
||||
public static override verboseName = msg("Property Mapping");
|
||||
public static override verboseNamePlural = msg("Property Mappings");
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-property-mapping-wizard": AKPropertyMappingWizard;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ import { msg } from "@lit/localize";
|
||||
* @prop {number} instancePk - The primary key of the instance to load.
|
||||
*/
|
||||
export abstract class BaseProviderForm<T extends object> extends ModelForm<T, number> {
|
||||
public static override verboseName = msg("Provider");
|
||||
public static override verboseNamePlural = msg("Providers");
|
||||
|
||||
public override getSuccessMessage(): string {
|
||||
return this.instance
|
||||
? msg("Successfully updated provider.")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "#admin/applications/ApplicationWizardHint";
|
||||
import "#admin/providers/ProviderWizard";
|
||||
import "#admin/providers/ak-provider-wizard";
|
||||
import "#admin/providers/google_workspace/GoogleWorkspaceProviderForm";
|
||||
import "#admin/providers/ldap/LDAPProviderForm";
|
||||
import "#admin/providers/microsoft_entra/MicrosoftEntraProviderForm";
|
||||
@@ -13,20 +13,20 @@ import "#admin/providers/ssf/SSFProviderFormPage";
|
||||
import "#admin/providers/wsfed/WSFederationProviderForm";
|
||||
import "#elements/buttons/SpinnerButton/index";
|
||||
import "#elements/forms/DeleteBulkForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
|
||||
import { CustomFormElementTagName } from "#elements/forms/unsafe";
|
||||
import { IconEditButtonByTagName } from "#elements/dialogs";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { StrictUnsafe } from "#elements/utils/unsafe";
|
||||
|
||||
import { AKProviderWizard } from "#admin/providers/ak-provider-wizard";
|
||||
|
||||
import { Provider, ProvidersApi } from "@goauthentik/api";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@@ -49,7 +49,7 @@ export class ProviderListPage extends TablePage<Provider> {
|
||||
public order = "name";
|
||||
|
||||
public searchLabel = msg("Provider Search");
|
||||
public searchPlaceholder = msg("Search for providers…");
|
||||
public searchPlaceholder = msg("Search for provider by name, type or assigned application...");
|
||||
|
||||
override async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
|
||||
return new ProvidersApi(DEFAULT_CONFIG).providersAllList(
|
||||
@@ -112,33 +112,24 @@ export class ProviderListPage extends TablePage<Provider> {
|
||||
return [
|
||||
html`<a href="#/core/providers/${item.pk}">${item.name}</a>`,
|
||||
this.#rowApp(item),
|
||||
html`${item.verboseName}`,
|
||||
html`<div>
|
||||
<ak-forms-modal>
|
||||
${StrictUnsafe<CustomFormElementTagName>(item.component, {
|
||||
slot: "form",
|
||||
instancePk: item.pk,
|
||||
submitLabel: msg("Save Changes"),
|
||||
headline: msg(str`Update ${item.verboseName}`, {
|
||||
id: "form.headline.update",
|
||||
}),
|
||||
})}
|
||||
<button
|
||||
aria-label=${msg(str`Edit "${item.name}" provider`)}
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-plain"
|
||||
>
|
||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||
<i aria-hidden="true" class="fas fa-edit"></i>
|
||||
</pf-tooltip>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
item.verboseName,
|
||||
html`<div class="ak-c-table__actions">
|
||||
${IconEditButtonByTagName(item.component, item.pk)}
|
||||
</div>`,
|
||||
];
|
||||
}
|
||||
|
||||
override renderObjectCreate(): TemplateResult {
|
||||
return html`<ak-provider-wizard> </ak-provider-wizard> `;
|
||||
protected override renderObjectCreate(): SlottedTemplateResult {
|
||||
return html`
|
||||
<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="button"
|
||||
aria-description="${msg("Open the wizard to create a new provider.")}"
|
||||
${AKProviderWizard.asModalInvoker()}
|
||||
>
|
||||
${msg("New Provider")}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user