mirror of
https://github.com/goauthentik/authentik
synced 2026-04-25 17:15:26 +02:00
web: Close modal on route navigation (#21622)
* Close dialog on navigation. * web: update dialog, form, and sidebar styles with logical properties and scroll shadows Migrate dialog padding CSS variables from physical (top/right/bottom/left) to logical (block-start/inline-end/block-end/inline-start) naming. Add scroll shadow utility class (ak-m-scroll-shadows) for scrollable regions. Rework radio input and form control styles including transparent backgrounds, checkbox-style indicators, and improved hover states. Refactor FormGroup marker to use CSS custom properties for open/closed states. Move sidebar padding from nav container to scrollable list. * web: refine elements and components for accessibility, type safety, and consistency Add ARIA role and label to dialog body, apply scroll shadow classes to modal body, sidebar nav, and wizard main. Update ak-status-label to support tri-state (good/bad/null) rendering with ts-pattern matching and a neutral label. Simplify FormGroup by removing wrapper div around default slot, adding part attributes for header styling, and changing description to nullable type. Clean up LogViewer and StaticTable with proper access modifiers, override annotations, and nullable item types. Simplify ak-switch-input checked binding and remove unused slot attribute from ak-radio-input help text. * web: modernize application pages with modalInvoker and updated form patterns Refactor ApplicationCheckAccessForm to use static form metadata properties (verboseName, submitVerb, createLabel), formatAPISuccessMessage, and a private CoreApi instance. Migrate ApplicationViewPage from ak-forms-modal slots to the modalInvoker directive for both edit and check-access actions. Accept nullable input in createPaginatedResponse for better null-safety. Fix casing of dropdown menu items in ApplicationListPage. * web: migrate remaining view pages to modalInvoker (#21592) * Fix visibility check, search params. * Add scroll shadow. * Partial revert of input layout. * Tidy groups. * Fix check access form invoker, styles. * Optional sizing. * Lowercase * Revise checkbox style. * Close dialog on navigation. * Fix padding. * Touch up shadow heights. * Migrate remaining view pages to modalInvoker, add e2e coverage. * Fix alignment. * Fix click handler, add placeholders. * Fix issue where form field is not serialized.
This commit is contained in:
@@ -4,8 +4,12 @@ import "#elements/forms/HorizontalFormElement";
|
|||||||
import "#elements/forms/SearchSelect/index";
|
import "#elements/forms/SearchSelect/index";
|
||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
|
import { PFSize } from "#common/enums";
|
||||||
|
import { APIMessage, MessageLevel } from "#common/messages";
|
||||||
|
|
||||||
import { Form } from "#elements/forms/Form";
|
import { Form } from "#elements/forms/Form";
|
||||||
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
import { ifPresent } from "#elements/utils/attributes";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Application,
|
Application,
|
||||||
@@ -16,13 +20,24 @@ import {
|
|||||||
} from "@goauthentik/api";
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, html, nothing, TemplateResult } from "lit";
|
import { CSSResult, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||||
|
|
||||||
@customElement("ak-application-check-access-form")
|
@customElement("ak-application-check-access-form")
|
||||||
export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
|
export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
|
||||||
|
public static override verboseName = msg("Access");
|
||||||
|
public static override submitVerb = msg("Check");
|
||||||
|
public static override createLabel = msg("Check");
|
||||||
|
public static override submittingVerb = msg("Checking");
|
||||||
|
|
||||||
|
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
|
||||||
|
|
||||||
|
#api = new CoreApi(DEFAULT_CONFIG);
|
||||||
|
|
||||||
|
public override size = PFSize.XLarge;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public application!: Application;
|
public application!: Application;
|
||||||
|
|
||||||
@@ -30,19 +45,27 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
|
|||||||
public result: PolicyTestResult | null = null;
|
public result: PolicyTestResult | null = null;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public request?: number;
|
public request: number | null = null;
|
||||||
|
|
||||||
getSuccessMessage(): string {
|
public override formatAPISuccessMessage(): APIMessage {
|
||||||
return msg("Successfully sent test-request.");
|
return {
|
||||||
|
level: MessageLevel.success,
|
||||||
|
message: msg("Successfully sent test-request."),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(data: { forUser: number }): Promise<PolicyTestResult> {
|
protected override send(data: { forUser: number }): Promise<PolicyTestResult> {
|
||||||
this.request = data.forUser;
|
this.request = data.forUser;
|
||||||
const result = await new CoreApi(DEFAULT_CONFIG).coreApplicationsCheckAccessRetrieve({
|
|
||||||
slug: this.application?.slug,
|
return this.#api
|
||||||
forUser: data.forUser,
|
.coreApplicationsCheckAccessRetrieve({
|
||||||
});
|
slug: this.application?.slug,
|
||||||
return (this.result = result);
|
forUser: data.forUser,
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.result = result;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override reset(): void {
|
public override reset(): void {
|
||||||
@@ -50,15 +73,14 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
|
|||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
|
protected renderResult(): SlottedTemplateResult {
|
||||||
|
const { passing, messages = [], logMessages = [] } = this.result || {};
|
||||||
|
|
||||||
renderResult(): TemplateResult {
|
return html`<ak-form-element-horizontal label=${msg("Passing")}>
|
||||||
return html`
|
|
||||||
<ak-form-element-horizontal label=${msg("Passing")}>
|
|
||||||
<div class="pf-c-form__group-label">
|
<div class="pf-c-form__group-label">
|
||||||
<div class="c-form__horizontal-group">
|
<div class="c-form__horizontal-group">
|
||||||
<span class="pf-c-form__label-text">
|
<span class="pf-c-form__label-text">
|
||||||
<ak-status-label ?good=${this.result?.passing}></ak-status-label>
|
<ak-status-label ?good=${ifPresent(passing)}></ak-status-label>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,54 +89,56 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
|
|||||||
<div class="pf-c-form__group-label">
|
<div class="pf-c-form__group-label">
|
||||||
<div class="c-form__horizontal-group">
|
<div class="c-form__horizontal-group">
|
||||||
<ul>
|
<ul>
|
||||||
${(this.result?.messages || []).length > 0
|
${messages.map((m) => {
|
||||||
? this.result?.messages?.map((m) => {
|
return html`<li>
|
||||||
return html`<li>
|
<span class="pf-c-form__label-text">${m}</span>
|
||||||
<span class="pf-c-form__label-text">${m}</span>
|
</li>`;
|
||||||
</li>`;
|
})}
|
||||||
})
|
|
||||||
: html`<li>
|
|
||||||
<span class="pf-c-form__label-text">-</span>
|
|
||||||
</li>`}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${msg("Log messages")}>
|
<ak-form-element-horizontal label=${msg("Log messages")}>
|
||||||
<ak-log-viewer .items=${this.result?.logMessages}></ak-log-viewer>
|
<ak-log-viewer .items=${logMessages}></ak-log-viewer>
|
||||||
</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="forUser">
|
return html`<ak-form-element-horizontal label=${msg("User")} required name="forUser">
|
||||||
<ak-search-select
|
<ak-search-select
|
||||||
|
placeholder=${msg("Select a user...")}
|
||||||
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
||||||
const args: CoreUsersListRequest = {
|
const args: CoreUsersListRequest = {
|
||||||
ordering: "username",
|
ordering: "username",
|
||||||
};
|
};
|
||||||
if (query !== undefined) {
|
|
||||||
|
if (query) {
|
||||||
args.search = query;
|
args.search = query;
|
||||||
}
|
}
|
||||||
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args);
|
|
||||||
|
const users = await this.#api.coreUsersList(args);
|
||||||
|
|
||||||
return users.results;
|
return users.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(user: User): string => {
|
.renderElement=${(user: User): string => {
|
||||||
return user.username;
|
return user.username;
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(user: User): TemplateResult => {
|
.renderDescription=${(user: User): SlottedTemplateResult => {
|
||||||
return html`${user.name}`;
|
return html`${user.name}`;
|
||||||
}}
|
}}
|
||||||
.value=${(user: User | undefined): number | undefined => {
|
.value=${(user: User | undefined): number | undefined => {
|
||||||
return user?.pk;
|
return user?.pk;
|
||||||
}}
|
}}
|
||||||
.selected=${(user: User): boolean => {
|
.selected=${(user: User): boolean => {
|
||||||
return user.pk.toString() === this.request?.toString();
|
return (
|
||||||
|
typeof this.request === "number" &&
|
||||||
|
user.pk.toString() === this.request.toString()
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
</ak-search-select>
|
</ak-search-select>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${this.result ? this.renderResult() : nothing}`;
|
${this.renderResult()}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
|||||||
"Opens the new application wizard, which will guide you through creating a new application with an existing provider.",
|
"Opens the new application wizard, which will guide you through creating a new application with an existing provider.",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
${msg("With New Provider...")}
|
${msg("with New Provider...")}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation">
|
<li role="presentation">
|
||||||
@@ -214,7 +214,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
|||||||
"Opens the new application form, which will guide you through creating a new application with an existing provider.",
|
"Opens the new application form, which will guide you through creating a new application with an existing provider.",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
${msg("With Existing Provider...")}
|
${msg("with Existing Provider...")}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -16,11 +16,15 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
|||||||
import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
|
import { APIError, parseAPIResponseError, pluckErrorDetail } from "#common/errors/network";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { WithLicenseSummary } from "#elements/mixins/license";
|
import { WithLicenseSummary } from "#elements/mixins/license";
|
||||||
|
|
||||||
import { setPageDetails } from "#components/ak-page-navbar";
|
import { setPageDetails } from "#components/ak-page-navbar";
|
||||||
import renderDescriptionList from "#components/DescriptionList";
|
import renderDescriptionList from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { ApplicationCheckAccessForm } from "#admin/applications/ApplicationCheckAccessForm";
|
||||||
|
import { ApplicationForm } from "#admin/applications/ApplicationForm";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Application,
|
Application,
|
||||||
ContentTypeEnum,
|
ContentTypeEnum,
|
||||||
@@ -181,36 +185,28 @@ export class ApplicationViewPage extends WithLicenseSummary(AKElement) {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
msg("Related actions"),
|
msg("Related actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-secondary pf-m-block"
|
||||||
<span slot="header"> ${msg("Update Application")} </span>
|
${modalInvoker(ApplicationForm, {
|
||||||
<ak-application-form
|
instancePk: this.application.slug,
|
||||||
slot="form"
|
})}
|
||||||
.instancePk=${this.application.slug}
|
>
|
||||||
>
|
${msg("Edit")}
|
||||||
</ak-application-form>
|
</button>
|
||||||
<button
|
<button
|
||||||
slot="trigger"
|
class="pf-c-button pf-m-secondary pf-m-block"
|
||||||
class="pf-c-button pf-m-secondary pf-m-block"
|
${modalInvoker(
|
||||||
>
|
ApplicationCheckAccessForm,
|
||||||
${msg("Edit")}
|
{
|
||||||
</button>
|
application: this.application,
|
||||||
</ak-forms-modal>
|
},
|
||||||
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
|
{
|
||||||
<span slot="submit">${msg("Check")}</span>
|
closedBy: "closerequest",
|
||||||
<span slot="header"> ${msg("Check Application access")} </span>
|
},
|
||||||
<ak-application-check-access-form
|
)}
|
||||||
slot="form"
|
>
|
||||||
.application=${this.application}
|
${msg("Check access")}
|
||||||
>
|
</button>
|
||||||
</ak-application-check-access-form>
|
|
||||||
<button
|
|
||||||
slot="trigger"
|
|
||||||
class="pf-c-button pf-m-secondary pf-m-block"
|
|
||||||
>
|
|
||||||
${msg("Check access")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>
|
|
||||||
${this.application.launchUrl
|
${this.application.launchUrl
|
||||||
? html`<a
|
? html`<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -220,7 +216,7 @@ export class ApplicationViewPage extends WithLicenseSummary(AKElement) {
|
|||||||
>
|
>
|
||||||
${msg("Launch")}
|
${msg("Launch")}
|
||||||
</a>`
|
</a>`
|
||||||
: nothing}`,
|
: null}`,
|
||||||
],
|
],
|
||||||
])}
|
])}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { SlottedTemplateResult } from "#elements/types";
|
|||||||
import { Provider, ProvidersApi } from "@goauthentik/api";
|
import { Provider, ProvidersApi } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { html } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
@customElement("ak-provider-table")
|
@customElement("ak-provider-table")
|
||||||
@@ -23,29 +22,24 @@ export class ProviderTable extends Table<Provider> {
|
|||||||
|
|
||||||
public override order = "name";
|
public override order = "name";
|
||||||
|
|
||||||
protected async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
|
protected override async apiEndpoint(): Promise<PaginatedResponse<Provider>> {
|
||||||
return new ProvidersApi(DEFAULT_CONFIG).providersAllList({
|
return new ProvidersApi(DEFAULT_CONFIG).providersAllList({
|
||||||
...(await this.defaultEndpointConfig()),
|
...(await this.defaultEndpointConfig()),
|
||||||
backchannel: this.backchannel,
|
backchannel: this.backchannel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected columns: TableColumn[] = [
|
protected override columns: TableColumn[] = [
|
||||||
// ---
|
// ---
|
||||||
[msg("Name"), "username"],
|
[msg("Name"), "username"],
|
||||||
[msg("Type")],
|
[msg("Type")],
|
||||||
];
|
];
|
||||||
|
|
||||||
protected row(item: Provider): SlottedTemplateResult[] {
|
protected override row(item: Provider): SlottedTemplateResult[] {
|
||||||
return [
|
return [item.name, item.verboseName];
|
||||||
html`<div>
|
|
||||||
<div>${item.name}</div>
|
|
||||||
</div>`,
|
|
||||||
html`${item.verboseName}`,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderSelectedChip(item: Provider): SlottedTemplateResult {
|
protected override renderSelectedChip(item: Provider): SlottedTemplateResult {
|
||||||
return item.name;
|
return item.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ export class AkBackchannelProvidersInput extends AKElement {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
|
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
|
||||||
|
|
||||||
<ak-provider-table backchannel></ak-provider-table>
|
<ak-provider-table backchannel></ak-provider-table>
|
||||||
</ak-form>
|
</ak-form>
|
||||||
`);
|
`);
|
||||||
@@ -100,7 +99,11 @@ export class AkBackchannelProvidersInput extends AKElement {
|
|||||||
<i class="fas fa-plus" aria-hidden="true"></i>
|
<i class="fas fa-plus" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="pf-c-form-control">
|
<div class="pf-c-form-control">
|
||||||
<ak-chip-group>${map(this.providers, renderOneChip)}</ak-chip-group>
|
<ak-chip-group
|
||||||
|
@click=${this.openSelectBackchannelProvidersModal}
|
||||||
|
placeholder=${msg("Select one or more backchannel providers...")}
|
||||||
|
>${map(this.providers, renderOneChip)}</ak-chip-group
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
|
${this.help ? html`<p class="pf-c-form__helper-text">${this.help}</p>` : nothing}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
|||||||
|
|
||||||
@customElement("ak-endpoints-device-form")
|
@customElement("ak-endpoints-device-form")
|
||||||
export class EndpointDeviceForm extends ModelForm<EndpointDevice, string> {
|
export class EndpointDeviceForm extends ModelForm<EndpointDevice, string> {
|
||||||
|
public static override verboseName = msg("Device");
|
||||||
|
public static override verboseNamePlural = msg("Devices");
|
||||||
loadInstance(pk: string): Promise<EndpointDevice> {
|
loadInstance(pk: string): Promise<EndpointDevice> {
|
||||||
return new EndpointsApi(DEFAULT_CONFIG).endpointsDevicesRetrieve({
|
return new EndpointsApi(DEFAULT_CONFIG).endpointsDevicesRetrieve({
|
||||||
deviceUuid: pk,
|
deviceUuid: pk,
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import "#elements/cards/AggregateCard";
|
import "#elements/cards/AggregateCard";
|
||||||
import "#elements/forms/DeleteBulkForm";
|
import "#elements/forms/DeleteBulkForm";
|
||||||
import "#admin/endpoints/devices/DeviceForm";
|
|
||||||
import "#admin/endpoints/devices/DeviceAddHowTo";
|
import "#admin/endpoints/devices/DeviceAddHowTo";
|
||||||
import "#elements/forms/ModalForm";
|
|
||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
|
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { PaginatedResponse, TableColumn, Timestamp } from "#elements/table/Table";
|
import { PaginatedResponse, TableColumn, Timestamp } from "#elements/table/Table";
|
||||||
import { TablePage } from "#elements/table/TablePage";
|
import { TablePage } from "#elements/table/TablePage";
|
||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
|
import { EndpointDeviceForm } from "#admin/endpoints/devices/DeviceForm";
|
||||||
|
|
||||||
import { DeviceSummary, EndpointDevice, EndpointsApi } from "@goauthentik/api";
|
import { DeviceSummary, EndpointDevice, EndpointsApi } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
@@ -132,17 +133,14 @@ export class DeviceListPage extends TablePage<EndpointDevice> {
|
|||||||
html`${item.facts.data.os?.name} ${item.facts.data.os?.version}`,
|
html`${item.facts.data.os?.name} ${item.facts.data.os?.version}`,
|
||||||
html`${item.accessGroupObj?.name || "-"}`,
|
html`${item.accessGroupObj?.name || "-"}`,
|
||||||
item.facts.created ? Timestamp(item.facts.created) : html`-`,
|
item.facts.created ? Timestamp(item.facts.created) : html`-`,
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-plain"
|
||||||
<span slot="header">${msg("Update Device")}</span>
|
${modalInvoker(EndpointDeviceForm, { instancePk: item.deviceUuid })}
|
||||||
<ak-endpoints-device-form slot="form" .instancePk=${item.deviceUuid}>
|
>
|
||||||
</ak-endpoints-device-form>
|
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
</pf-tooltip>
|
||||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
</button>`,
|
||||||
</pf-tooltip>
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,20 +5,20 @@ import "#admin/endpoints/devices/facts/DeviceProcessTable";
|
|||||||
import "#admin/endpoints/devices/facts/DeviceUserTable";
|
import "#admin/endpoints/devices/facts/DeviceUserTable";
|
||||||
import "#admin/endpoints/devices/facts/DeviceSoftwareTable";
|
import "#admin/endpoints/devices/facts/DeviceSoftwareTable";
|
||||||
import "#admin/endpoints/devices/facts/DeviceGroupTable";
|
import "#admin/endpoints/devices/facts/DeviceGroupTable";
|
||||||
import "#admin/endpoints/devices/DeviceForm";
|
|
||||||
import "#admin/endpoints/devices/DeviceEvents";
|
import "#admin/endpoints/devices/DeviceEvents";
|
||||||
import "#elements/forms/ModalForm";
|
|
||||||
import "#elements/Tabs";
|
import "#elements/Tabs";
|
||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
import { APIError, parseAPIResponseError } from "#common/errors/network";
|
import { APIError, parseAPIResponseError } from "#common/errors/network";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { Timestamp } from "#elements/table/shared";
|
import { Timestamp } from "#elements/table/shared";
|
||||||
|
|
||||||
import { setPageDetails } from "#components/ak-page-navbar";
|
import { setPageDetails } from "#components/ak-page-navbar";
|
||||||
import renderDescriptionList, { DescriptionPair } from "#components/DescriptionList";
|
import renderDescriptionList, { DescriptionPair } from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { EndpointDeviceForm } from "#admin/endpoints/devices/DeviceForm";
|
||||||
import { getSize, osFamilyToLabel, trySortNumerical } from "#admin/endpoints/devices/utils";
|
import { getSize, osFamilyToLabel, trySortNumerical } from "#admin/endpoints/devices/utils";
|
||||||
|
|
||||||
import { DeviceConnection, Disk, EndpointDeviceDetails, EndpointsApi } from "@goauthentik/api";
|
import { DeviceConnection, Disk, EndpointDeviceDetails, EndpointsApi } from "@goauthentik/api";
|
||||||
@@ -124,18 +124,14 @@ export class DeviceViewPage extends AKElement {
|
|||||||
[msg("Device access group"), this.device?.accessGroupObj?.name ?? "-"],
|
[msg("Device access group"), this.device?.accessGroupObj?.name ?? "-"],
|
||||||
[
|
[
|
||||||
msg("Actions"),
|
msg("Actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-primary"
|
||||||
<span slot="header">${msg("Update Device")}</span>
|
${modalInvoker(EndpointDeviceForm, {
|
||||||
<ak-endpoints-device-form
|
instancePk: this.device?.deviceUuid,
|
||||||
slot="form"
|
})}
|
||||||
.instancePk=${this.device?.deviceUuid}
|
>
|
||||||
>
|
${msg("Edit")}
|
||||||
</ak-endpoints-device-form>
|
</button>`,
|
||||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
|
||||||
${msg("Edit")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
{ horizontal: true },
|
{ horizontal: true },
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import "#admin/flows/BoundStagesList";
|
import "#admin/flows/BoundStagesList";
|
||||||
import "#admin/flows/FlowDiagram";
|
import "#admin/flows/FlowDiagram";
|
||||||
import "#admin/flows/FlowForm";
|
|
||||||
import "#admin/policies/BoundPoliciesList";
|
import "#admin/policies/BoundPoliciesList";
|
||||||
import "#admin/rbac/ak-rbac-object-permission-page";
|
import "#admin/rbac/ak-rbac-object-permission-page";
|
||||||
import "#admin/events/ObjectChangelog";
|
import "#admin/events/ObjectChangelog";
|
||||||
@@ -11,11 +10,13 @@ import { AndNext, DEFAULT_CONFIG } from "#common/api/config";
|
|||||||
import { isResponseErrorLike } from "#common/errors/network";
|
import { isResponseErrorLike } from "#common/errors/network";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
import { setPageDetails } from "#components/ak-page-navbar";
|
import { setPageDetails } from "#components/ak-page-navbar";
|
||||||
import renderDescriptionList from "#components/DescriptionList";
|
import renderDescriptionList from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { FlowForm } from "#admin/flows/FlowForm";
|
||||||
import { DesignationToLabel } from "#admin/flows/utils";
|
import { DesignationToLabel } from "#admin/flows/utils";
|
||||||
|
|
||||||
import { Flow, FlowsApi, ModelEnum } from "@goauthentik/api";
|
import { Flow, FlowsApi, ModelEnum } from "@goauthentik/api";
|
||||||
@@ -97,21 +98,14 @@ export class FlowViewPage extends AKElement {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
msg("Related actions"),
|
msg("Related actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-block pf-m-secondary"
|
||||||
<span slot="header"> ${msg("Update Flow")} </span>
|
${modalInvoker(FlowForm, {
|
||||||
<ak-flow-form
|
instancePk: this.flow.slug,
|
||||||
slot="form"
|
})}
|
||||||
.instancePk=${this.flow.slug}
|
>
|
||||||
>
|
${msg("Edit")}
|
||||||
</ak-flow-form>
|
</button>
|
||||||
<button
|
|
||||||
slot="trigger"
|
|
||||||
class="pf-c-button pf-m-block pf-m-secondary"
|
|
||||||
>
|
|
||||||
${msg("Edit")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>
|
|
||||||
<a
|
<a
|
||||||
class="pf-c-button pf-m-block pf-m-secondary"
|
class="pf-c-button pf-m-block pf-m-secondary"
|
||||||
href=${this.flow.exportUrl}
|
href=${this.flow.exportUrl}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import "#admin/groups/ak-group-form";
|
|
||||||
import "#admin/groups/RelatedUserList";
|
import "#admin/groups/RelatedUserList";
|
||||||
import "#admin/rbac/ak-rbac-object-permission-page";
|
import "#admin/rbac/ak-rbac-object-permission-page";
|
||||||
import "#admin/roles/ak-related-role-table";
|
import "#admin/roles/ak-related-role-table";
|
||||||
@@ -10,19 +9,21 @@ import "#elements/CodeMirror";
|
|||||||
import "#elements/Tabs";
|
import "#elements/Tabs";
|
||||||
import "#elements/buttons/ActionButton/index";
|
import "#elements/buttons/ActionButton/index";
|
||||||
import "#elements/buttons/SpinnerButton/index";
|
import "#elements/buttons/SpinnerButton/index";
|
||||||
import "#elements/forms/ModalForm";
|
|
||||||
import "#elements/ak-mdx/ak-mdx";
|
import "#elements/ak-mdx/ak-mdx";
|
||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
import { EVENT_REFRESH } from "#common/constants";
|
import { EVENT_REFRESH } from "#common/constants";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { WithLicenseSummary } from "#elements/mixins/license";
|
import { WithLicenseSummary } from "#elements/mixins/license";
|
||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
import { setPageDetails } from "#components/ak-page-navbar";
|
import { setPageDetails } from "#components/ak-page-navbar";
|
||||||
import renderDescriptionList from "#components/DescriptionList";
|
import renderDescriptionList from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { GroupForm } from "#admin/groups/ak-group-form";
|
||||||
|
|
||||||
import { ContentTypeEnum, CoreApi, Group, ModelEnum } from "@goauthentik/api";
|
import { ContentTypeEnum, CoreApi, Group, ModelEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
@@ -150,18 +151,14 @@ export class GroupViewPage extends WithLicenseSummary(AKElement) {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
msg("Related actions"),
|
msg("Related actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-primary pf-m-block"
|
||||||
<span slot="header">${msg("Update Group")}</span>
|
${modalInvoker(GroupForm, {
|
||||||
<ak-group-form slot="form" .instancePk=${this.group.pk}>
|
instancePk: this.group.pk,
|
||||||
</ak-group-form>
|
})}
|
||||||
<button
|
>
|
||||||
slot="trigger"
|
${msg("Edit")}
|
||||||
class="pf-m-primary pf-c-button pf-m-block"
|
</button>`,
|
||||||
>
|
|
||||||
${msg("Edit")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`,
|
|
||||||
],
|
],
|
||||||
])}
|
])}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import "#admin/groups/ak-group-form";
|
|
||||||
import "#admin/users/ak-user-group-table";
|
import "#admin/users/ak-user-group-table";
|
||||||
import "#components/ak-status-label";
|
import "#components/ak-status-label";
|
||||||
import "#elements/buttons/SpinnerButton/index";
|
import "#elements/buttons/SpinnerButton/index";
|
||||||
import "#elements/forms/DeleteBulkForm";
|
import "#elements/forms/DeleteBulkForm";
|
||||||
import "#elements/forms/HorizontalFormElement";
|
import "#elements/forms/HorizontalFormElement";
|
||||||
import "#elements/forms/ModalForm";
|
|
||||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
|
|
||||||
import { renderModal } from "#elements/dialogs";
|
import { modalInvoker, renderModal } from "#elements/dialogs";
|
||||||
import { AKFormSubmitEvent, Form } from "#elements/forms/Form";
|
import { AKFormSubmitEvent, Form } from "#elements/forms/Form";
|
||||||
import { PaginatedResponse, Table, TableColumn } from "#elements/table/Table";
|
import { PaginatedResponse, Table, TableColumn } from "#elements/table/Table";
|
||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
|
import { GroupForm } from "#admin/groups/ak-group-form";
|
||||||
|
|
||||||
import { CoreApi, Group, User } from "@goauthentik/api";
|
import { CoreApi, Group, User } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
@@ -23,6 +23,10 @@ import { ifDefined } from "lit/directives/if-defined.js";
|
|||||||
|
|
||||||
@customElement("ak-group-related-add")
|
@customElement("ak-group-related-add")
|
||||||
export class RelatedGroupAdd extends Form<{ groups: string[] }> {
|
export class RelatedGroupAdd extends Form<{ groups: string[] }> {
|
||||||
|
public static override verboseName = msg("Group");
|
||||||
|
public static override submitVerb = msg("Add");
|
||||||
|
public static override createLabel = msg("Add");
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public user?: User;
|
public user?: User;
|
||||||
|
|
||||||
@@ -74,7 +78,10 @@ export class RelatedGroupAdd extends Form<{ groups: string[] }> {
|
|||||||
</pf-tooltip>
|
</pf-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<div class="pf-c-form-control">
|
<div class="pf-c-form-control">
|
||||||
<ak-chip-group>
|
<ak-chip-group
|
||||||
|
@click=${this.openUserGroupSelectModal}
|
||||||
|
placeholder=${msg("Select one or more groups...")}
|
||||||
|
>
|
||||||
${this.groupsToAdd.map((group) => {
|
${this.groupsToAdd.map((group) => {
|
||||||
return html`<ak-chip
|
return html`<ak-chip
|
||||||
removable
|
removable
|
||||||
@@ -151,40 +158,30 @@ export class RelatedGroupList extends Table<Group> {
|
|||||||
return [
|
return [
|
||||||
html`<a href="#/identity/groups/${item.pk}">${item.name}</a>`,
|
html`<a href="#/identity/groups/${item.pk}">${item.name}</a>`,
|
||||||
html`<ak-status-label type="neutral" ?good=${item.isSuperuser}></ak-status-label>`,
|
html`<ak-status-label type="neutral" ?good=${item.isSuperuser}></ak-status-label>`,
|
||||||
html` <ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-plain"
|
||||||
<span slot="header">${msg("Update Group")}</span>
|
${modalInvoker(GroupForm, { instancePk: item.pk })}
|
||||||
<ak-group-form slot="form" .instancePk=${item.pk}> </ak-group-form>
|
>
|
||||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
<pf-tooltip position="top" content=${msg("Edit")}>
|
||||||
<pf-tooltip position="top" content=${msg("Edit")}>
|
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
</pf-tooltip>
|
||||||
</pf-tooltip>
|
</button>`,
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToolbar(): TemplateResult {
|
renderToolbar(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${this.targetUser
|
${this.targetUser
|
||||||
? html`<ak-forms-modal>
|
? html`<button
|
||||||
<span slot="submit">${msg("Add")}</span>
|
class="pf-c-button pf-m-primary"
|
||||||
<span slot="header">${msg("Add Group")}</span>
|
${modalInvoker(RelatedGroupAdd, { user: this.targetUser })}
|
||||||
<ak-group-related-add .user=${this.targetUser} slot="form">
|
>
|
||||||
</ak-group-related-add>
|
${msg("Add to existing group")}
|
||||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
</button>`
|
||||||
${msg("Add to existing group")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`
|
|
||||||
: nothing}
|
: nothing}
|
||||||
<ak-forms-modal>
|
<button class="pf-c-button pf-m-secondary" ${modalInvoker(GroupForm)}>
|
||||||
<span slot="submit">${msg("Create")}</span>
|
${msg("Add new group")}
|
||||||
<span slot="header">${msg("Create Group")}</span>
|
</button>
|
||||||
<ak-group-form slot="form"> </ak-group-form>
|
|
||||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
|
||||||
${msg("Add new group")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>
|
|
||||||
${super.renderToolbar()}
|
${super.renderToolbar()}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import "#admin/providers/RelatedApplicationButton";
|
import "#admin/providers/RelatedApplicationButton";
|
||||||
import "#admin/providers/oauth2/OAuth2ProviderForm";
|
|
||||||
import "#admin/events/ObjectChangelog";
|
import "#admin/events/ObjectChangelog";
|
||||||
import "#admin/rbac/ak-rbac-object-permission-page";
|
import "#admin/rbac/ak-rbac-object-permission-page";
|
||||||
import "#admin/rbac/ObjectPermissionModal";
|
import "#admin/rbac/ObjectPermissionModal";
|
||||||
@@ -15,10 +14,13 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
|||||||
import { EVENT_REFRESH } from "#common/constants";
|
import { EVENT_REFRESH } from "#common/constants";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
import renderDescriptionList from "#components/DescriptionList";
|
import renderDescriptionList from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { OAuth2ProviderFormPage } from "#admin/providers/oauth2/OAuth2ProviderForm";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ClientTypeEnum,
|
ClientTypeEnum,
|
||||||
CoreApi,
|
CoreApi,
|
||||||
@@ -238,21 +240,14 @@ export class OAuth2ProviderViewPage extends AKElement {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
msg("Related actions"),
|
msg("Related actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-primary pf-m-block"
|
||||||
<span slot="header">${msg("Update OAuth2 Provider")}</span>
|
${modalInvoker(OAuth2ProviderFormPage, {
|
||||||
<ak-provider-oauth2-form
|
instancePk: this.provider?.pk || 0,
|
||||||
slot="form"
|
})}
|
||||||
.instancePk=${this.provider?.pk || 0}
|
>
|
||||||
>
|
${msg("Edit")}
|
||||||
</ak-provider-oauth2-form>
|
</button>`,
|
||||||
<button
|
|
||||||
slot="trigger"
|
|
||||||
class="pf-c-button pf-m-primary pf-m-block"
|
|
||||||
>
|
|
||||||
${msg("Edit")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`,
|
|
||||||
],
|
],
|
||||||
])}
|
])}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import "#admin/providers/RelatedApplicationButton";
|
import "#admin/providers/RelatedApplicationButton";
|
||||||
import "#admin/providers/ssf/SSFProviderFormPage";
|
|
||||||
import "#admin/providers/ssf/StreamTable";
|
import "#admin/providers/ssf/StreamTable";
|
||||||
import "#admin/events/ObjectChangelog";
|
import "#admin/events/ObjectChangelog";
|
||||||
import "#admin/rbac/ObjectPermissionModal";
|
import "#admin/rbac/ObjectPermissionModal";
|
||||||
@@ -14,10 +13,13 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
|||||||
import { EVENT_REFRESH } from "#common/constants";
|
import { EVENT_REFRESH } from "#common/constants";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
import renderDescriptionList from "#components/DescriptionList";
|
import renderDescriptionList from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { SSFProviderFormPage } from "#admin/providers/ssf/SSFProviderFormPage";
|
||||||
|
|
||||||
import { ModelEnum, ProvidersApi, SSFProvider } from "@goauthentik/api";
|
import { ModelEnum, ProvidersApi, SSFProvider } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
@@ -155,15 +157,14 @@ export class SSFProviderViewPage extends AKElement {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
msg("Related actions"),
|
msg("Related actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-primary pf-m-block"
|
||||||
<span slot="header">${msg("Update SSF Provider")}</span>
|
${modalInvoker(SSFProviderFormPage, {
|
||||||
<ak-provider-ssf-form slot="form" .instancePk=${this.provider.pk}>
|
instancePk: this.provider.pk,
|
||||||
</ak-provider-ssf-form>
|
})}
|
||||||
<button slot="trigger" class="pf-c-button pf-m-primary pf-m-block">
|
>
|
||||||
${msg("Edit")}
|
${msg("Edit")}
|
||||||
</button>
|
</button>`,
|
||||||
</ak-forms-modal>`,
|
|
||||||
],
|
],
|
||||||
])}
|
])}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -83,7 +83,10 @@ export class AddRelatedRoleForm extends Form<{ roles: string[] }> {
|
|||||||
</pf-tooltip>
|
</pf-tooltip>
|
||||||
</button>
|
</button>
|
||||||
<div class="pf-c-form-control">
|
<div class="pf-c-form-control">
|
||||||
<ak-chip-group>
|
<ak-chip-group
|
||||||
|
@click=${this.openRolesSelectionModal}
|
||||||
|
placeholder=${msg("Select one or more roles...")}
|
||||||
|
>
|
||||||
${this.rolesToAdd.map((role) => {
|
${this.rolesToAdd.map((role) => {
|
||||||
return html`<ak-chip
|
return html`<ak-chip
|
||||||
removable
|
removable
|
||||||
|
|||||||
@@ -85,7 +85,10 @@ export class RolePermissionForm extends ModelForm<RolePermissionAssign, number>
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="pf-c-form-control">
|
<div class="pf-c-form-control">
|
||||||
<ak-chip-group>
|
<ak-chip-group
|
||||||
|
@click=${this.openSelectPermissionsModal}
|
||||||
|
placeholder=${msg("Select one or more permissions...")}
|
||||||
|
>
|
||||||
${this.permissionsToAdd.map((permission) => {
|
${this.permissionsToAdd.map((permission) => {
|
||||||
return html`<ak-chip
|
return html`<ak-chip
|
||||||
removable
|
removable
|
||||||
|
|||||||
@@ -2,21 +2,22 @@ import "#admin/groups/RelatedGroupList";
|
|||||||
import "#admin/groups/RelatedUserList";
|
import "#admin/groups/RelatedUserList";
|
||||||
import "#admin/rbac/ak-rbac-object-permission-page";
|
import "#admin/rbac/ak-rbac-object-permission-page";
|
||||||
import "#admin/lifecycle/ObjectLifecyclePage";
|
import "#admin/lifecycle/ObjectLifecyclePage";
|
||||||
import "#admin/roles/ak-role-form";
|
|
||||||
import "#admin/events/ObjectChangelog";
|
import "#admin/events/ObjectChangelog";
|
||||||
import "#admin/events/UserEvents";
|
import "#admin/events/UserEvents";
|
||||||
import "#elements/Tabs";
|
import "#elements/Tabs";
|
||||||
import "#elements/forms/ModalForm";
|
|
||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
import { EVENT_REFRESH } from "#common/constants";
|
import { EVENT_REFRESH } from "#common/constants";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { WithLicenseSummary } from "#elements/mixins/license";
|
import { WithLicenseSummary } from "#elements/mixins/license";
|
||||||
|
|
||||||
import { setPageDetails } from "#components/ak-page-navbar";
|
import { setPageDetails } from "#components/ak-page-navbar";
|
||||||
import { renderDescriptionList } from "#components/DescriptionList";
|
import { renderDescriptionList } from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { RoleForm } from "#admin/roles/ak-role-form";
|
||||||
|
|
||||||
import { ContentTypeEnum, ModelEnum, RbacApi, Role } from "@goauthentik/api";
|
import { ContentTypeEnum, ModelEnum, RbacApi, Role } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
@@ -99,21 +100,14 @@ export class RoleViewPage extends WithLicenseSummary(AKElement) {
|
|||||||
[msg("Name"), this.targetRole.name],
|
[msg("Name"), this.targetRole.name],
|
||||||
[
|
[
|
||||||
msg("Related actions"),
|
msg("Related actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-primary pf-m-block"
|
||||||
<span slot="header">${msg("Update Role")}</span>
|
${modalInvoker(RoleForm, {
|
||||||
<ak-role-form
|
instancePk: this.targetRole.pk,
|
||||||
slot="form"
|
})}
|
||||||
.instancePk=${this.targetRole.pk}
|
>
|
||||||
>
|
${msg("Edit")}
|
||||||
</ak-role-form>
|
</button>`,
|
||||||
<button
|
|
||||||
slot="trigger"
|
|
||||||
class="pf-c-button pf-m-primary pf-m-block"
|
|
||||||
>
|
|
||||||
${msg("Edit")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`,
|
|
||||||
],
|
],
|
||||||
])}
|
])}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { ModelForm } from "#elements/forms/ModelForm";
|
|||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
|
|
||||||
export abstract class BaseSourceForm<T extends object = object> extends ModelForm<T, string> {
|
export abstract class BaseSourceForm<T extends object = object> extends ModelForm<T, string> {
|
||||||
|
public static override verboseName = msg("Source");
|
||||||
|
public static override verboseNamePlural = msg("Sources");
|
||||||
getSuccessMessage(): string {
|
getSuccessMessage(): string {
|
||||||
return this.instance
|
return this.instance
|
||||||
? msg("Successfully updated source.")
|
? msg("Successfully updated source.")
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import "#admin/rbac/ak-rbac-object-permission-page";
|
import "#admin/rbac/ak-rbac-object-permission-page";
|
||||||
import "#admin/sources/ldap/LDAPSourceConnectivity";
|
import "#admin/sources/ldap/LDAPSourceConnectivity";
|
||||||
import "#admin/sources/ldap/LDAPSourceForm";
|
|
||||||
import "#admin/sources/ldap/LDAPSourceUserList";
|
import "#admin/sources/ldap/LDAPSourceUserList";
|
||||||
import "#admin/sources/ldap/LDAPSourceGroupList";
|
import "#admin/sources/ldap/LDAPSourceGroupList";
|
||||||
import "#admin/events/ObjectChangelog";
|
import "#admin/events/ObjectChangelog";
|
||||||
@@ -8,7 +7,6 @@ import "#elements/CodeMirror";
|
|||||||
import "#elements/Tabs";
|
import "#elements/Tabs";
|
||||||
import "#elements/buttons/ActionButton/index";
|
import "#elements/buttons/ActionButton/index";
|
||||||
import "#elements/buttons/SpinnerButton/index";
|
import "#elements/buttons/SpinnerButton/index";
|
||||||
import "#elements/forms/ModalForm";
|
|
||||||
import "#elements/sync/SyncStatusCard";
|
import "#elements/sync/SyncStatusCard";
|
||||||
import "#elements/tasks/ScheduleList";
|
import "#elements/tasks/ScheduleList";
|
||||||
|
|
||||||
@@ -16,10 +14,13 @@ import { DEFAULT_CONFIG } from "#common/api/config";
|
|||||||
import { EVENT_REFRESH } from "#common/constants";
|
import { EVENT_REFRESH } from "#common/constants";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
import renderDescriptionList from "#components/DescriptionList";
|
import renderDescriptionList from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { LDAPSourceForm } from "#admin/sources/ldap/LDAPSourceForm";
|
||||||
|
|
||||||
import { LDAPSource, ModelEnum, SourcesApi } from "@goauthentik/api";
|
import { LDAPSource, ModelEnum, SourcesApi } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
@@ -105,23 +106,14 @@ export class LDAPSourceViewPage extends AKElement {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
msg("Related actions"),
|
msg("Related actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-primary pf-m-block"
|
||||||
<span slot="header"
|
${modalInvoker(LDAPSourceForm, {
|
||||||
>${msg("Update LDAP Source")}</span
|
instancePk: this.source?.slug,
|
||||||
>
|
})}
|
||||||
<ak-source-ldap-form
|
>
|
||||||
slot="form"
|
${msg("Edit")}
|
||||||
.instancePk=${this.source?.slug}
|
</button>`,
|
||||||
>
|
|
||||||
</ak-source-ldap-form>
|
|
||||||
<button
|
|
||||||
slot="trigger"
|
|
||||||
class="pf-c-button pf-m-primary pf-m-block"
|
|
||||||
>
|
|
||||||
${msg("Edit")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
{ twocolumn: true },
|
{ twocolumn: true },
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import "#admin/policies/BoundPoliciesList";
|
import "#admin/policies/BoundPoliciesList";
|
||||||
import "#admin/rbac/ak-rbac-object-permission-page";
|
import "#admin/rbac/ak-rbac-object-permission-page";
|
||||||
import "#admin/sources/oauth/OAuthSourceDiagram";
|
import "#admin/sources/oauth/OAuthSourceDiagram";
|
||||||
import "#admin/sources/oauth/OAuthSourceForm";
|
|
||||||
import "#admin/events/ObjectChangelog";
|
import "#admin/events/ObjectChangelog";
|
||||||
import "#elements/CodeMirror";
|
import "#elements/CodeMirror";
|
||||||
import "#elements/Tabs";
|
import "#elements/Tabs";
|
||||||
import "#elements/buttons/SpinnerButton/index";
|
import "#elements/buttons/SpinnerButton/index";
|
||||||
import "#elements/forms/ModalForm";
|
|
||||||
|
|
||||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||||
import { EVENT_REFRESH } from "#common/constants";
|
import { EVENT_REFRESH } from "#common/constants";
|
||||||
|
|
||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { modalInvoker } from "#elements/dialogs";
|
||||||
import { sourceBindingTypeNotices } from "#elements/sources/utils";
|
import { sourceBindingTypeNotices } from "#elements/sources/utils";
|
||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
import renderDescriptionList from "#components/DescriptionList";
|
import renderDescriptionList from "#components/DescriptionList";
|
||||||
|
|
||||||
|
import { OAuthSourceForm } from "#admin/sources/oauth/OAuthSourceForm";
|
||||||
|
|
||||||
import { ModelEnum, OAuthSource, ProviderTypeEnum, SourcesApi } from "@goauthentik/api";
|
import { ModelEnum, OAuthSource, ProviderTypeEnum, SourcesApi } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
@@ -137,21 +138,14 @@ export class OAuthSourceViewPage extends AKElement {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
msg("Related actions"),
|
msg("Related actions"),
|
||||||
html`<ak-forms-modal>
|
html`<button
|
||||||
<span slot="submit">${msg("Save Changes")}</span>
|
class="pf-c-button pf-m-primary pf-m-block"
|
||||||
<span slot="header">${msg("Update OAuth Source")}</span>
|
${modalInvoker(OAuthSourceForm, {
|
||||||
<ak-source-oauth-form
|
instancePk: this.source.slug,
|
||||||
slot="form"
|
})}
|
||||||
.instancePk=${this.source.slug}
|
>
|
||||||
>
|
${msg("Edit")}
|
||||||
</ak-source-oauth-form>
|
</button>`,
|
||||||
<button
|
|
||||||
slot="trigger"
|
|
||||||
class="pf-c-button pf-m-primary pf-m-block"
|
|
||||||
>
|
|
||||||
${msg("Edit")}
|
|
||||||
</button>
|
|
||||||
</ak-forms-modal>`,
|
|
||||||
],
|
],
|
||||||
])}
|
])}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -251,10 +251,19 @@ export class UserListPage extends WithBrandConfig(
|
|||||||
const displayName = formatUserDisplayName(item);
|
const displayName = formatUserDisplayName(item);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
html`<img class="pf-c-avatar pf-m-hidden pf-m-visible-on-xl" src=${item.avatar} />`,
|
html`<img
|
||||||
html`<a href="#/identity/users/${item.pk}">
|
class="pf-c-avatar pf-m-hidden pf-m-visible-on-xl"
|
||||||
<div>${item.username}</div>
|
src=${item.avatar}
|
||||||
<small>${item.name ? item.name : html`<${msg("No name set")}>`}</small>
|
alt=${msg(str`Avatar for ${displayName}`)}
|
||||||
|
/>`,
|
||||||
|
html`<a
|
||||||
|
href="#/identity/users/${item.pk}"
|
||||||
|
aria-label=${msg(str`View details for ${displayName}`)}
|
||||||
|
>
|
||||||
|
<div aria-label=${msg(str`Username: ${item.username}`)}>${item.username}</div>
|
||||||
|
<small aria-label=${msg(str`Display name: ${displayName || msg("No name set")}`)}
|
||||||
|
>${displayName ? item.name : html`<${msg("No name set")}>`}</small
|
||||||
|
>
|
||||||
</a>`,
|
</a>`,
|
||||||
html`<ak-status-label ?good=${item.isActive}></ak-status-label>`,
|
html`<ak-status-label ?good=${item.isActive}></ak-status-label>`,
|
||||||
Timestamp(item.lastLogin),
|
Timestamp(item.lastLogin),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { SlottedTemplateResult } from "#elements/types";
|
|||||||
import { CoreApi, Group } from "@goauthentik/api";
|
import { CoreApi, Group } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, html, nothing } from "lit";
|
import { CSSResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||||
@@ -26,44 +26,44 @@ export class UserGroupTable extends Table<Group> {
|
|||||||
|
|
||||||
public override order = "name";
|
public override order = "name";
|
||||||
|
|
||||||
protected async apiEndpoint(): Promise<PaginatedResponse<Group>> {
|
protected override async apiEndpoint(): Promise<PaginatedResponse<Group>> {
|
||||||
return new CoreApi(DEFAULT_CONFIG).coreGroupsList({
|
return new CoreApi(DEFAULT_CONFIG).coreGroupsList({
|
||||||
...(await this.defaultEndpointConfig()),
|
...(await this.defaultEndpointConfig()),
|
||||||
includeUsers: false,
|
includeUsers: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected columns: TableColumn[] = [
|
protected override columns: TableColumn[] = [
|
||||||
[msg("Name"), "username"],
|
[msg("Name"), "username"],
|
||||||
[msg("Superuser"), "is_superuser"],
|
[msg("Superuser"), "is_superuser"],
|
||||||
[msg("Members"), ""],
|
[msg("Members"), ""],
|
||||||
];
|
];
|
||||||
|
|
||||||
protected row(item: Group): SlottedTemplateResult[] {
|
protected override row(item: Group): SlottedTemplateResult[] {
|
||||||
return [
|
return [
|
||||||
html`<div>${item.name}</div>`,
|
item.name,
|
||||||
html`<ak-status-label type="neutral" ?good=${item.isSuperuser}></ak-status-label>`,
|
html`<ak-status-label type="neutral" ?good=${item.isSuperuser}></ak-status-label>`,
|
||||||
html`${(item.users || []).length}`,
|
item.users?.length || 0,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderSelectedChip(item: Group): SlottedTemplateResult {
|
protected override renderSelectedChip(item: Group): SlottedTemplateResult {
|
||||||
return item.name;
|
return item.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override render(): SlottedTemplateResult {
|
protected override render(): SlottedTemplateResult {
|
||||||
const willSuperuser = this.selectedElements.filter((g) => g.isSuperuser).length;
|
const willSuperuser = this.selectedElements.some((g) => g.isSuperuser);
|
||||||
|
|
||||||
return html`${willSuperuser
|
if (!willSuperuser) {
|
||||||
? html`
|
return super.render();
|
||||||
<div class="pf-c-banner pf-m-warning">
|
}
|
||||||
${msg(
|
|
||||||
"Warning: Adding the user to the selected group(s) will give them superuser permissions.",
|
return html`<div class="pf-c-banner pf-m-warning">
|
||||||
)}
|
${msg(
|
||||||
</div>
|
"Warning: Adding the user to the selected group(s) will give them superuser permissions.",
|
||||||
`
|
)}
|
||||||
: nothing}
|
</div>
|
||||||
${super.render()}`;
|
${super.render()}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ export interface PaginatedResponse<T, A extends object = object> {
|
|||||||
* @param input An iterable of items to include in the results array.
|
* @param input An iterable of items to include in the results array.
|
||||||
*/
|
*/
|
||||||
export function createPaginatedResponse<T = unknown, A extends object = object>(
|
export function createPaginatedResponse<T = unknown, A extends object = object>(
|
||||||
input: Iterable<T> = [],
|
input?: Iterable<T> | null,
|
||||||
): PaginatedResponse<T, A> {
|
): PaginatedResponse<T, A> {
|
||||||
const results = Array.from(input);
|
const results = Array.from(input ?? []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pagination: {
|
pagination: {
|
||||||
|
|||||||
@@ -34,12 +34,7 @@ export class AkRadioInput<T extends Jsonifiable> extends HorizontalLightComponen
|
|||||||
const helpText = this.help?.trim();
|
const helpText = this.help?.trim();
|
||||||
|
|
||||||
return html`${helpText
|
return html`${helpText
|
||||||
? html`<p
|
? html`<p part="radio-help" class="pf-c-form__helper-radio" id=${this.helpID}>
|
||||||
part="radio-help"
|
|
||||||
class="pf-c-form__helper-radio"
|
|
||||||
id=${this.helpID}
|
|
||||||
slot="label-end"
|
|
||||||
>
|
|
||||||
${helpText}
|
${helpText}
|
||||||
</p>`
|
</p>`
|
||||||
: null}<ak-radio
|
: null}<ak-radio
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { AKElement } from "#elements/Base";
|
import { AKElement } from "#elements/Base";
|
||||||
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
import Styles from "#components/ak-status-label.css";
|
import Styles from "#components/ak-status-label.css";
|
||||||
|
|
||||||
import { P4Disposition } from "#styles/patternfly/constants";
|
import { P4Disposition } from "#styles/patternfly/constants";
|
||||||
|
|
||||||
|
import { match, P } from "ts-pattern";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
@@ -43,30 +46,35 @@ export class AkStatusLabel extends AKElement {
|
|||||||
static styles = [PFLabel, Styles];
|
static styles = [PFLabel, Styles];
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
good = false;
|
public good: boolean | null = null;
|
||||||
|
|
||||||
@property({ type: String, attribute: "good-label" })
|
@property({ type: String, attribute: "good-label" })
|
||||||
goodLabel = msg("Yes");
|
public goodLabel = msg("Yes");
|
||||||
|
|
||||||
@property({ type: String, attribute: "bad-label" })
|
@property({ type: String, attribute: "bad-label" })
|
||||||
badLabel = msg("No");
|
public badLabel = msg("No");
|
||||||
|
|
||||||
|
@property({ type: String, attribute: "neutral-label" })
|
||||||
|
public neutralLabel = msg("-");
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
compact = false;
|
public compact = false;
|
||||||
|
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
type: P4Disposition = P4Disposition.Error;
|
public type: P4Disposition = P4Disposition.Error;
|
||||||
|
|
||||||
render() {
|
protected override render(): SlottedTemplateResult {
|
||||||
const details = statusToDetails.get(this.type);
|
const details = statusToDetails.get(this.type);
|
||||||
|
|
||||||
if (!details) {
|
if (!details) {
|
||||||
throw new TypeError(`Bad status type [${this.type}] passed to ak-status-label`);
|
throw new TypeError(`Bad status type [${this.type}] passed to ak-status-label`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [label, color, icon] = this.good
|
const [label, color, icon] = match(this.good)
|
||||||
? [this.goodLabel, "pf-m-green", "fa-check"]
|
.with(P.nullish, () => [this.neutralLabel, "pf-m-gray", "fa-question"] as const)
|
||||||
: [this.badLabel, ...details];
|
.with(true, () => [this.goodLabel, "pf-m-green", "fa-check"] as const)
|
||||||
|
.with(false, () => [this.badLabel, ...details] as const)
|
||||||
|
.exhaustive();
|
||||||
|
|
||||||
const classes = {
|
const classes = {
|
||||||
"pf-c-label": true,
|
"pf-c-label": true,
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ export class AkSwitchInput extends AKElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const doCheck = this.checked ? this.checked : undefined;
|
|
||||||
|
|
||||||
return html`<ak-form-element-horizontal name=${this.name} ?required=${this.required}>
|
return html`<ak-form-element-horizontal name=${this.name} ?required=${this.required}>
|
||||||
<label class="pf-c-switch" for="${this.#fieldID}">
|
<label class="pf-c-switch" for="${this.#fieldID}">
|
||||||
<input
|
<input
|
||||||
@@ -69,7 +67,7 @@ export class AkSwitchInput extends AKElement {
|
|||||||
aria-describedby="${this.#fieldID}-help"
|
aria-describedby="${this.#fieldID}-help"
|
||||||
class="pf-c-switch__input"
|
class="pf-c-switch__input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
?checked=${doCheck}
|
?checked=${this.checked}
|
||||||
/>
|
/>
|
||||||
<span class="pf-c-switch__toggle">
|
<span class="pf-c-switch__toggle">
|
||||||
<span class="pf-c-switch__toggle-icon">
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ export abstract class WizardStep extends AKElement {
|
|||||||
</aside>
|
</aside>
|
||||||
<main
|
<main
|
||||||
part="wizard-main"
|
part="wizard-main"
|
||||||
class="pf-c-wizard__main ak-m-thin-scrollbar"
|
class="pf-c-wizard__main ak-m-thin-scrollbar ak-m-scroll-shadows"
|
||||||
aria-label=${msg("Wizard content")}
|
aria-label=${msg("Wizard content")}
|
||||||
>
|
>
|
||||||
<div id="main-content" class="pf-c-wizard__main-body">
|
<div id="main-content" class="pf-c-wizard__main-body">
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding-top: var(--ak-c-dialog__header--PaddingTop);
|
padding-block-start: var(--ak-c-dialog__header--PaddingBlockStart);
|
||||||
padding-bottom: var(--ak-c-dialog__header--PaddingBottom);
|
padding-block-end: var(--ak-c-dialog__header--PaddingBlockEnd);
|
||||||
padding-right: var(--ak-c-dialog__header--PaddingRight);
|
padding-inline-end: var(--ak-c-dialog__header--PaddingInlineEnd);
|
||||||
padding-left: var(--ak-c-dialog__header--PaddingLeft);
|
padding-inline-start: var(--ak-c-dialog__header--PaddingInlineStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ak-c-dialog__header.pf-m-help {
|
.ak-c-dialog__header.pf-m-help {
|
||||||
@@ -32,12 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ak-c-dialog__header:last-child {
|
.ak-c-dialog__header:last-child {
|
||||||
padding-bottom: var(--ak-c-dialog__header--last-child--PaddingBottom);
|
padding-block-end: var(--ak-c-dialog__header--last-child--PaddingBlockEnd);
|
||||||
}
|
|
||||||
|
|
||||||
.ak-c-dialog__header + .ak-c-dialog__body,
|
|
||||||
.ak-c-dialog__header + slot + .ak-c-dialog__body {
|
|
||||||
--ak-c-dialog__body--PaddingTop: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ak-c-dialog__header-main {
|
.ak-c-dialog__header-main {
|
||||||
@@ -73,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ak-c-dialog__description {
|
.ak-c-dialog__description {
|
||||||
padding-top: var(--ak-c-dialog__description--PaddingTop);
|
padding-block-start: var(--ak-c-dialog__description--PaddingBlockStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
@@ -82,9 +77,10 @@
|
|||||||
|
|
||||||
.ak-c-dialog__body {
|
.ak-c-dialog__body {
|
||||||
min-height: var(--ak-c-dialog__body--MinHeight);
|
min-height: var(--ak-c-dialog__body--MinHeight);
|
||||||
padding-top: var(--ak-c-dialog__body--PaddingTop);
|
padding-block-start: var(--ak-c-dialog__body--PaddingBlockStart);
|
||||||
padding-right: var(--ak-c-dialog__body--PaddingRight);
|
padding-inline-end: var(--ak-c-dialog__body--PaddingInlineEnd);
|
||||||
padding-left: var(--ak-c-dialog__body--PaddingLeft);
|
padding-inline-start: var(--ak-c-dialog__body--PaddingInlineStart);
|
||||||
|
padding-block-end: var(--ak-c-dialog__body--PaddingBlockEnd);
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
@@ -93,7 +89,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ak-c-dialog__body:last-child {
|
.ak-c-dialog__body:last-child {
|
||||||
padding-bottom: var(--ak-c-dialog__body--last-child--PaddingBottom);
|
padding-block-end: var(--ak-c-dialog__body--last-child--PaddingBlockEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ak-c-dialog__footer {
|
.ak-c-dialog__footer {
|
||||||
@@ -102,14 +98,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
|
|
||||||
margin-block-start: var(--pf-global--spacer--md);
|
padding-block-start: var(--ak-c-dialog__footer--PaddingBlockStart);
|
||||||
|
padding-inline-end: var(--ak-c-dialog__footer--PaddingInlineEnd);
|
||||||
|
padding-block-end: var(--ak-c-dialog__footer--PaddingBlockEnd);
|
||||||
|
padding-inline-start: var(--ak-c-dialog__footer--PaddingInlineStart);
|
||||||
|
|
||||||
padding-top: var(--ak-c-dialog__footer--PaddingTop);
|
|
||||||
padding-right: var(--ak-c-dialog__footer--PaddingRight);
|
|
||||||
padding-bottom: var(--ak-c-dialog__footer--PaddingBottom);
|
|
||||||
padding-left: var(--ak-c-dialog__footer--PaddingLeft);
|
|
||||||
|
|
||||||
box-shadow: var(--ak-c-dialog__footer--BoxShadow);
|
|
||||||
gap: var(--pf-global--spacer--xs);
|
gap: var(--pf-global--spacer--xs);
|
||||||
|
|
||||||
@media screen and (min-width: 576px) {
|
@media screen and (min-width: 576px) {
|
||||||
|
|||||||
@@ -312,9 +312,16 @@ export class AKModal extends AKElement implements TransclusionParentElement {
|
|||||||
this.beforeBodySlot.name = "before-body";
|
this.beforeBodySlot.name = "before-body";
|
||||||
|
|
||||||
this.dialogBody = this.ownerDocument.createElement("div");
|
this.dialogBody = this.ownerDocument.createElement("div");
|
||||||
this.dialogBody.classList.add("ak-c-dialog__body", "ak-m-thin-scrollbar");
|
this.dialogBody.classList.add(
|
||||||
|
"ak-c-dialog__body",
|
||||||
|
"ak-m-thin-scrollbar",
|
||||||
|
"ak-m-scroll-shadows",
|
||||||
|
);
|
||||||
this.dialogBody.setAttribute("part", "body");
|
this.dialogBody.setAttribute("part", "body");
|
||||||
|
|
||||||
|
this.dialogBody.role = "region";
|
||||||
|
this.dialogBody.ariaLabel = msg("Dialog content");
|
||||||
|
|
||||||
this.dialogBody.appendChild(this.defaultSlot);
|
this.dialogBody.appendChild(this.defaultSlot);
|
||||||
|
|
||||||
this.addEventListener("command", (event) => {
|
this.addEventListener("command", (event) => {
|
||||||
@@ -379,6 +386,9 @@ export class AKModal extends AKElement implements TransclusionParentElement {
|
|||||||
nextSlottedElement.visible = true;
|
nextSlottedElement.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextSlottedElement.classList.add("ak-c-dialog__slotted-content");
|
||||||
|
|
||||||
|
this.slottedElement?.classList.remove("ak-c-dialog__slotted-content");
|
||||||
this.slottedElement = nextSlottedElement;
|
this.slottedElement = nextSlottedElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,12 +83,12 @@
|
|||||||
/* #region Header */
|
/* #region Header */
|
||||||
|
|
||||||
.ak-c-dialog {
|
.ak-c-dialog {
|
||||||
--ak-c-dialog__header--PaddingTop: var(--pf-global--spacer--lg);
|
--ak-c-dialog__header--PaddingBlockStart: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__header--PaddingBottom: var(--pf-global--spacer--lg);
|
--ak-c-dialog__header--PaddingBlockEnd: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__header--PaddingRight: var(--pf-global--spacer--lg);
|
--ak-c-dialog__header--PaddingInlineEnd: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__header--PaddingLeft: var(--pf-global--spacer--lg);
|
--ak-c-dialog__header--PaddingInlineStart: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__header--last-child--PaddingBottom: var(--pf-global--spacer--lg);
|
--ak-c-dialog__header--last-child--PaddingBlockEnd: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__header--body--PaddingTop: var(--pf-global--spacer--md);
|
--ak-c-dialog__header--body--PaddingBlockStart: var(--pf-global--spacer--md);
|
||||||
|
|
||||||
--ak-c-dialog__title--LineHeight: var(--pf-global--LineHeight--sm);
|
--ak-c-dialog__title--LineHeight: var(--pf-global--LineHeight--sm);
|
||||||
--ak-c-dialog__title--FontFamily: var(--pf-global--FontFamily--heading--sans-serif);
|
--ak-c-dialog__title--FontFamily: var(--pf-global--FontFamily--heading--sans-serif);
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
--ak-c-dialog__title-icon--MarginRight: var(--pf-global--spacer--sm);
|
--ak-c-dialog__title-icon--MarginRight: var(--pf-global--spacer--sm);
|
||||||
--ak-c-dialog__title-icon--Color: var(--pf-global--Color--100);
|
--ak-c-dialog__title-icon--Color: var(--pf-global--Color--100);
|
||||||
|
|
||||||
--ak-c-dialog__description--PaddingTop: var(--pf-global--spacer--xs);
|
--ak-c-dialog__description--PaddingBlockStart: var(--pf-global--spacer--xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
@@ -108,10 +108,11 @@
|
|||||||
var(--pf-global--FontSize--md) * var(--pf-global--LineHeight--md)
|
var(--pf-global--FontSize--md) * var(--pf-global--LineHeight--md)
|
||||||
);
|
);
|
||||||
|
|
||||||
--ak-c-dialog__body--PaddingTop: var(--pf-global--spacer--lg);
|
--ak-c-dialog__body--PaddingBlockStart: var(--pf-global--spacer--md);
|
||||||
--ak-c-dialog__body--PaddingRight: var(--pf-global--spacer--lg);
|
--ak-c-dialog__body--PaddingBlockEnd: var(--pf-global--spacer--md);
|
||||||
--ak-c-dialog__body--PaddingLeft: var(--pf-global--spacer--lg);
|
--ak-c-dialog__body--PaddingInlineEnd: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__body--last-child--PaddingBottom: var(--pf-global--spacer--lg);
|
--ak-c-dialog__body--PaddingInlineStart: var(--pf-global--spacer--lg);
|
||||||
|
--ak-c-dialog__body--last-child--PaddingBlockEnd: var(--pf-global--spacer--lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
@@ -119,12 +120,10 @@
|
|||||||
/* #region Footer */
|
/* #region Footer */
|
||||||
|
|
||||||
.ak-c-dialog {
|
.ak-c-dialog {
|
||||||
--ak-c-dialog__footer--PaddingTop: var(--pf-global--spacer--lg);
|
--ak-c-dialog__footer--PaddingBlockStart: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__footer--PaddingRight: var(--pf-global--spacer--lg);
|
--ak-c-dialog__footer--PaddingInlineEnd: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__footer--PaddingBottom: var(--pf-global--spacer--lg);
|
--ak-c-dialog__footer--PaddingBlockEnd: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__footer--PaddingLeft: var(--pf-global--spacer--lg);
|
--ak-c-dialog__footer--PaddingInlineStart: var(--pf-global--spacer--lg);
|
||||||
--ak-c-dialog__footer--BoxShadow: inset 0 0.5px 0
|
|
||||||
var(--pf-global--BackgroundColor--dark-transparent-200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
@@ -283,11 +282,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ak-c-dialog__content:has([part*="wizard"]) {
|
.ak-c-dialog__content:has([part*="wizard"]) {
|
||||||
|
--ak-c-dialog__body--PaddingInlineEnd: 0;
|
||||||
|
--ak-c-dialog__body--PaddingInlineStart: 0;
|
||||||
|
--ak-c-dialog__body--last-child--PaddingBlockEnd: 0;
|
||||||
|
--ak-c-dialog__body--PaddingBlockStart: 0;
|
||||||
|
--ak-c-dialog__body--PaddingBlockEnd: 0;
|
||||||
|
--ak-m-scroll-shadows--BorderWidth: 0;
|
||||||
|
|
||||||
grid-template-rows: [body] auto;
|
grid-template-rows: [body] auto;
|
||||||
--ak-c-dialog__body--PaddingRight: 0;
|
|
||||||
--ak-c-dialog__body--PaddingLeft: 0;
|
|
||||||
--ak-c-dialog__body--last-child--PaddingBottom: 0;
|
|
||||||
--ak-c-dialog__body--PaddingTop: 0;
|
|
||||||
|
|
||||||
::part(body) {
|
::part(body) {
|
||||||
overscroll-behavior: none;
|
overscroll-behavior: none;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import "#elements/dialogs/ak-modal";
|
|||||||
import { AKRefreshEvent } from "#common/events";
|
import { AKRefreshEvent } from "#common/events";
|
||||||
|
|
||||||
import { DialogInit } from "#elements/dialogs/shared";
|
import { DialogInit } from "#elements/dialogs/shared";
|
||||||
|
import { RouteChangeEvent } from "#elements/router/events";
|
||||||
import { ifPresent } from "#elements/utils/attributes";
|
import { ifPresent } from "#elements/utils/attributes";
|
||||||
|
|
||||||
import { html, render } from "lit";
|
import { html, render } from "lit";
|
||||||
@@ -89,6 +90,8 @@ export function renderDialog(
|
|||||||
onDispose,
|
onDispose,
|
||||||
}: DialogInit = {},
|
}: DialogInit = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const eventAbortController = new AbortController();
|
||||||
|
|
||||||
const dialog = ownerDocument.createElement("dialog");
|
const dialog = ownerDocument.createElement("dialog");
|
||||||
dialog.classList.add("ak-c-dialog", ...classList);
|
dialog.classList.add("ak-c-dialog", ...classList);
|
||||||
dialog.closedBy = closedBy;
|
dialog.closedBy = closedBy;
|
||||||
@@ -121,8 +124,15 @@ export function renderDialog(
|
|||||||
setDialogCountAttribute(-1, ownerDocument);
|
setDialogCountAttribute(-1, ownerDocument);
|
||||||
|
|
||||||
onDispose?.(event);
|
onDispose?.(event);
|
||||||
|
eventAbortController.abort();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.addEventListener(RouteChangeEvent.eventName, dispose, {
|
||||||
|
passive: true,
|
||||||
|
once: true,
|
||||||
|
signal: eventAbortController.signal,
|
||||||
|
});
|
||||||
|
|
||||||
dialog.addEventListener("close", dispose, {
|
dialog.addEventListener("close", dispose, {
|
||||||
passive: true,
|
passive: true,
|
||||||
once: true,
|
once: true,
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ import { SlottedTemplateResult } from "#elements/types";
|
|||||||
import { LogEvent, LogLevelEnum } from "@goauthentik/api";
|
import { LogEvent, LogLevelEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, html, nothing, TemplateResult } from "lit";
|
import { CSSResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||||
|
|
||||||
@customElement("ak-log-viewer")
|
@customElement("ak-log-viewer")
|
||||||
export class LogViewer extends StaticTable<LogEvent> {
|
export class LogViewer extends StaticTable<LogEvent> {
|
||||||
expandable = true;
|
public static styles: CSSResult[] = [...super.styles, PFDescriptionList];
|
||||||
|
|
||||||
static styles: CSSResult[] = [...super.styles, PFDescriptionList];
|
public override expandable = true;
|
||||||
|
|
||||||
protected override renderEmpty(): SlottedTemplateResult {
|
protected override renderEmpty(): SlottedTemplateResult {
|
||||||
return super.renderEmpty(
|
return super.renderEmpty(
|
||||||
@@ -28,7 +28,7 @@ export class LogViewer extends StaticTable<LogEvent> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderExpanded(item: LogEvent): TemplateResult {
|
protected override renderExpanded(item: LogEvent): SlottedTemplateResult {
|
||||||
return html`<dl class="pf-c-description-list pf-m-horizontal">
|
return html`<dl class="pf-c-description-list pf-m-horizontal">
|
||||||
<div class="pf-c-description-list__group">
|
<div class="pf-c-description-list__group">
|
||||||
<dt class="pf-c-description-list__term">
|
<dt class="pf-c-description-list__term">
|
||||||
@@ -53,8 +53,8 @@ export class LogViewer extends StaticTable<LogEvent> {
|
|||||||
</dl>`;
|
</dl>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToolbarContainer(): SlottedTemplateResult {
|
protected override renderToolbarContainer(): SlottedTemplateResult {
|
||||||
return nothing;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected columns: TableColumn[] = [
|
protected columns: TableColumn[] = [
|
||||||
@@ -64,7 +64,7 @@ export class LogViewer extends StaticTable<LogEvent> {
|
|||||||
[msg("Logger")],
|
[msg("Logger")],
|
||||||
];
|
];
|
||||||
|
|
||||||
statusForItem(item: LogEvent): string {
|
protected statusForItem(item: LogEvent): string {
|
||||||
switch (item.logLevel) {
|
switch (item.logLevel) {
|
||||||
case LogLevelEnum.Critical:
|
case LogLevelEnum.Critical:
|
||||||
case LogLevelEnum.Error:
|
case LogLevelEnum.Error:
|
||||||
@@ -82,15 +82,15 @@ export class LogViewer extends StaticTable<LogEvent> {
|
|||||||
return formatElapsedTime(item.timestamp);
|
return formatElapsedTime(item.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
row(item: LogEvent): SlottedTemplateResult[] {
|
protected override row(item: LogEvent): SlottedTemplateResult[] {
|
||||||
return [
|
return [
|
||||||
html`<ak-timestamp .timestamp=${item.timestamp} refresh></ak-timestamp>`,
|
html`<ak-timestamp .timestamp=${item.timestamp} refresh></ak-timestamp>`,
|
||||||
html`<ak-status-label
|
html`<ak-status-label
|
||||||
type=${this.statusForItem(item)}
|
type=${this.statusForItem(item)}
|
||||||
bad-label=${item.logLevel}
|
bad-label=${item.logLevel}
|
||||||
></ak-status-label>`,
|
></ak-status-label>`,
|
||||||
html`${item.event}`,
|
item.event,
|
||||||
html`${item.logger}`,
|
item.logger,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -467,10 +467,16 @@ export class Form<T = Record<string, unknown>, D = T>
|
|||||||
|
|
||||||
const assignedElements = this.defaultSlot.assignedElements({ flatten: true });
|
const assignedElements = this.defaultSlot.assignedElements({ flatten: true });
|
||||||
|
|
||||||
const [firstAssignedElement] = assignedElements;
|
const formFields = assignedElements.filter(isFormField);
|
||||||
|
|
||||||
if (assignedElements.length === 1 && isFormField(firstAssignedElement)) {
|
if (formFields.length) {
|
||||||
return firstAssignedElement.toJSON() as D;
|
if (formFields.length === 1) {
|
||||||
|
return formFields[0].toJSON() as D;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(
|
||||||
|
`Multiple form-associated elements found in the form, but no "ak-form-element-horizontal" elements found. Unable to determine which element(s) to serialize.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const namedElements = assignedElements.filter((element): element is AKElement => {
|
const namedElements = assignedElements.filter((element): element is AKElement => {
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
:host([theme="dark"]) {
|
:host {
|
||||||
--ak-c-form-group__marker--Color: var(--pf-global--Color--300);
|
--ak-c-form-group__marker--Color: var(--pf-global--Color--200);
|
||||||
--ak-c-form-group__marker--ColorHover: var(--pf-global--Color--200);
|
--ak-c-form-group__marker--ColorHover: var(--pf-global--Color--100);
|
||||||
|
--ak-c-form-group__marker--Opacity: 75%;
|
||||||
|
|
||||||
|
--ak-c-form-group--marker--Content: "\f105";
|
||||||
|
--ak-c-form-group--marker--ContentOpen: "\f107";
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([theme="dark"]) {
|
||||||
details {
|
details {
|
||||||
@media (prefers-contrast: more) {
|
@media (prefers-contrast: more) {
|
||||||
background: var(--pf-global--BackgroundColor--150);
|
background: var(--pf-global--BackgroundColor--150);
|
||||||
@@ -10,63 +16,79 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
details {
|
details {
|
||||||
|
&[open] {
|
||||||
|
--ak-c-form-group--marker--Content: var(--ak-c-form-group--marker--ContentOpen);
|
||||||
|
--ak-c-form-group__marker--Opacity: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-contrast: more) {
|
@media (prefers-contrast: more) {
|
||||||
border: 1px solid var(--pf-global--BorderColor--200);
|
border: 1px solid var(--pf-global--BorderColor--200);
|
||||||
background: var(--pf-global--BackgroundColor--150);
|
background: var(--pf-global--BackgroundColor--150);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&::details-content {
|
details::details-content {
|
||||||
padding-inline-start: calc(
|
padding-inline-start: calc(
|
||||||
var(--pf-global--spacer--lg) + var(--pf-global--spacer--form-element)
|
var(--pf-global--spacer--lg) + var(--pf-global--spacer--form-element)
|
||||||
);
|
);
|
||||||
padding-inline-end: var(--pf-global--spacer--md);
|
padding-inline-end: var(--pf-global--spacer--md);
|
||||||
padding-block-end: var(--pf-global--spacer--sm);
|
padding-block-end: var(--pf-global--spacer--sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > summary {
|
details > summary:hover {
|
||||||
backdrop-filter: var(--ak-c-dialog__backdrop--BackdropFilter);
|
--ak-c-form-group__marker--Color: var(--ak-c-form-group__marker--ColorHover);
|
||||||
|
}
|
||||||
|
|
||||||
inset-block-start: 0;
|
details[open] > summary:hover {
|
||||||
position: sticky;
|
--ak-c-form-group__marker--Opacity: 100%;
|
||||||
background: var(--ak-c-form-group--BackgroundColor, transparent);
|
}
|
||||||
z-index: var(--ak-c-form-group--ZIndex, 1);
|
|
||||||
|
|
||||||
list-style-position: outside;
|
details > summary {
|
||||||
margin-inline-start: var(--pf-global--spacer--sm);
|
background: var(--ak-c-form-group--BackgroundColor, transparent);
|
||||||
padding-inline-start: calc(var(--pf-global--spacer--md) + 0.25rem);
|
|
||||||
padding-block: var(--pf-global--spacer-xs);
|
|
||||||
padding-inline: var(--pf-global--spacer--md);
|
|
||||||
list-style-type: "\f105";
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
font-weight: bold;
|
list-style-position: outside;
|
||||||
|
margin-inline-start: var(--pf-global--spacer--sm);
|
||||||
|
padding-inline-start: calc(var(--pf-global--spacer--md) + 0.25rem);
|
||||||
|
padding-block: var(--pf-global--spacer-xs);
|
||||||
|
padding-inline: var(--pf-global--spacer--md);
|
||||||
|
list-style-type: var(--ak-c-form-group--marker--Content);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
@media (prefers-contrast: more) {
|
@media (prefers-contrast: more) {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
margin-inline-start: var(--pf-global--spacer--lg);
|
margin-inline-start: var(--pf-global--spacer--lg);
|
||||||
padding-block: var(--pf-global--spacer--sm);
|
padding-block: var(--pf-global--spacer--sm);
|
||||||
}
|
|
||||||
|
|
||||||
&::marker {
|
|
||||||
color: var(--ak-c-form-group__marker--Color, var(--pf-global--Color--200));
|
|
||||||
transition: var(--pf-c-form__field-group-toggle-icon--Transition);
|
|
||||||
font-family: "Font Awesome 5 Free";
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover::marker {
|
|
||||||
color: var(--ak-c-form-group__marker--ColorHover, var(--pf-global--Color--100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[open] summary {
|
|
||||||
list-style-type: "\f107";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
details summary::marker {
|
||||||
|
content: var(--ak-c-form-group--marker--Content, "\f105");
|
||||||
|
color: color-mix(
|
||||||
|
var(--ak-c-form-group__marker--Color) var(--ak-c-form-group__marker--Opacity),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
transition: var(--pf-c-form__field-group-toggle-icon--Transition);
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
font-weight: 900;
|
||||||
|
opacity: var(--ak-c-form-group__marker--Opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="group-header-title"] {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[part="form-group-header-title"] {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--pf-global--spacer--sm);
|
||||||
|
backdrop-filter: var(--ak-c-dialog__backdrop--BackdropFilter);
|
||||||
|
}
|
||||||
|
|
||||||
[part="label"] {
|
[part="label"] {
|
||||||
padding: var(--pf-global--spacer--xs);
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-form__field-group-header-description {
|
.pf-c-form__field-group-header-description {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export class AKFormGroup extends AKElement {
|
|||||||
public label = msg("Details");
|
public label = msg("Details");
|
||||||
|
|
||||||
@property({ type: String, reflect: true })
|
@property({ type: String, reflect: true })
|
||||||
public description?: string;
|
public description: string | null = null;
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
@@ -126,10 +126,14 @@ export class AKFormGroup extends AKElement {
|
|||||||
aria-describedby="form-group-expandable-content-description"
|
aria-describedby="form-group-expandable-content-description"
|
||||||
>
|
>
|
||||||
<summary @click=${this.toggle}>
|
<summary @click=${this.toggle}>
|
||||||
<div class="pf-c-form__field-group-header-main">
|
<div class="pf-c-form__field-group-header-main" part="group-header">
|
||||||
<header class="pf-c-form__field-group-header-title">
|
<header
|
||||||
|
class="pf-c-form__field-group-header-title"
|
||||||
|
part="group-header-title"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="pf-c-form__field-group-header-title-text"
|
class="pf-c-form__field-group-header-title-text"
|
||||||
|
part="form-group-header-title"
|
||||||
id="form-group-header-title"
|
id="form-group-header-title"
|
||||||
role="heading"
|
role="heading"
|
||||||
aria-level="3"
|
aria-level="3"
|
||||||
@@ -138,7 +142,6 @@ export class AKFormGroup extends AKElement {
|
|||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="pf-c-form__field-group-header-description"
|
class="pf-c-form__field-group-header-description"
|
||||||
data-test-id="form-group-header-description"
|
data-test-id="form-group-header-description"
|
||||||
@@ -149,9 +152,7 @@ export class AKFormGroup extends AKElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</summary>
|
</summary>
|
||||||
<div id="form-group-expandable-content">
|
<slot></slot>
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</details>
|
</details>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,4 +22,5 @@
|
|||||||
|
|
||||||
.pf-c-radio__description {
|
.pf-c-radio__description {
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
|
text-wrap: pretty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-nav {
|
.pf-c-nav {
|
||||||
|
--ak-m-scroll-shadows--BorderStyle: none;
|
||||||
--ak-c-nav__item--BorderColor: var(--pf-global--BorderColor--100);
|
--ak-c-nav__item--BorderColor: var(--pf-global--BorderColor--100);
|
||||||
--pf-c-nav__subnav__link--hover--after--BorderColor: var(--ak-accent);
|
--pf-c-nav__subnav__link--hover--after--BorderColor: var(--ak-accent);
|
||||||
|
|
||||||
@@ -25,7 +26,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
padding-block-start: var(--pf-global--spacer--sm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-nav__section + .pf-c-nav__section {
|
.pf-c-nav__section + .pf-c-nav__section {
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
.pf-c-nav__list {
|
.pf-c-nav__list {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
padding-block-start: var(--pf-global--spacer--sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-nav__link.pf-m-current::after,
|
.pf-c-nav__link.pf-m-current::after,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class Sidebar extends AKElement {
|
|||||||
?hidden=${this.hidden}
|
?hidden=${this.hidden}
|
||||||
aria-label=${msg("Global navigation")}
|
aria-label=${msg("Global navigation")}
|
||||||
role="navigation"
|
role="navigation"
|
||||||
class="pf-c-nav__list"
|
class="pf-c-nav__list ak-m-thin-scrollbar ak-m-scroll-shadows"
|
||||||
part="list"
|
part="list"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|||||||
@@ -4,31 +4,31 @@ import { PaginatedResponse, Table } from "#elements/table/Table";
|
|||||||
import { SlottedTemplateResult } from "#elements/types";
|
import { SlottedTemplateResult } from "#elements/types";
|
||||||
|
|
||||||
import { PropertyValues } from "lit";
|
import { PropertyValues } from "lit";
|
||||||
import { html, nothing } from "lit-html";
|
|
||||||
import { property } from "lit/decorators.js";
|
import { property } from "lit/decorators.js";
|
||||||
|
|
||||||
export abstract class StaticTable<T extends object> extends Table<T> {
|
export abstract class StaticTable<T extends object> extends Table<T> {
|
||||||
protected override searchEnabled = false;
|
protected override searchEnabled = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
items?: T[] = [];
|
public items: T[] | null = [];
|
||||||
|
|
||||||
protected override async apiEndpoint(): Promise<PaginatedResponse<T, object>> {
|
protected override async apiEndpoint(): Promise<PaginatedResponse<T, object>> {
|
||||||
return createPaginatedResponse(this.items ?? []);
|
return createPaginatedResponse(this.items ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override renderToolbar(): SlottedTemplateResult {
|
protected override renderToolbar(): SlottedTemplateResult {
|
||||||
return html`${this.renderObjectCreate()}`;
|
return this.renderObjectCreate();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override renderTablePagination(): SlottedTemplateResult {
|
protected override renderTablePagination(): SlottedTemplateResult {
|
||||||
return nothing;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override willUpdate(changedProperties: PropertyValues<this>): void {
|
protected override willUpdate(changedProperties: PropertyValues<this>): void {
|
||||||
if (changedProperties.has("items")) {
|
if (changedProperties.has("items")) {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
super.willUpdate(changedProperties);
|
super.willUpdate(changedProperties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,3 +181,67 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
.ak-m-scroll-shadows {
|
||||||
|
--ak-m-scroll-shadows--BorderWidth--light: 0.5px;
|
||||||
|
--ak-m-scroll-shadows--BorderWidth--dark: 1px;
|
||||||
|
--ak-m-scroll-shadows--Translucency: 75%;
|
||||||
|
--ak-m-scroll-shadows--BackgroundColor: var(--pf-global--BackgroundColor--100, white);
|
||||||
|
--ak-m-scroll-shadows--ShadowColor: var(--pf-global--Color--400);
|
||||||
|
--ak-m-scroll-shadows--ShadowColorMix: color-mix(
|
||||||
|
var(--ak-m-scroll-shadows--ShadowColor),
|
||||||
|
transparent var(--ak-m-scroll-shadows--Translucency)
|
||||||
|
);
|
||||||
|
|
||||||
|
--ak-m-scroll-shadows--ShadowLength: 2.75rem;
|
||||||
|
--ak-m-scroll-shadow--ShadowDepth: 1rem;
|
||||||
|
|
||||||
|
background-image:
|
||||||
|
linear-gradient(var(--ak-m-scroll-shadows--BackgroundColor) 30%, transparent),
|
||||||
|
linear-gradient(transparent, var(--ak-m-scroll-shadows--BackgroundColor) 70%),
|
||||||
|
radial-gradient(
|
||||||
|
farthest-side at 50% 0px,
|
||||||
|
var(--ak-m-scroll-shadows--ShadowColorMix),
|
||||||
|
transparent
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
farthest-side at 50% 100%,
|
||||||
|
var(--ak-m-scroll-shadows--ShadowColorMix),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
|
||||||
|
background-position-x: center;
|
||||||
|
background-position-y: top, bottom;
|
||||||
|
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size:
|
||||||
|
100% var(--ak-m-scroll-shadows--ShadowLength),
|
||||||
|
100% var(--ak-m-scroll-shadows--ShadowLength),
|
||||||
|
100% var(--ak-m-scroll-shadow--ShadowDepth),
|
||||||
|
100% var(--ak-m-scroll-shadow--ShadowDepth);
|
||||||
|
background-attachment: local, local, scroll, scroll;
|
||||||
|
|
||||||
|
border-block-color: var(
|
||||||
|
--ak-m-scroll-shadows--BorderColor,
|
||||||
|
color-mix(
|
||||||
|
var(--ak-m-scroll-shadows--ShadowColor),
|
||||||
|
transparent var(--ak-m-scroll-shadows--Translucency)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
border-block-style: var(--ak-m-scroll-shadows--BorderStyle, solid);
|
||||||
|
border-block-width: var(
|
||||||
|
--ak-m-scroll-shadows--BorderWidth,
|
||||||
|
var(--ak-m-scroll-shadows--BorderWidth--light)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"],
|
||||||
|
:host([theme="dark"]) {
|
||||||
|
.ak-m-scroll-shadows {
|
||||||
|
--ak-m-scroll-shadow--ShadowDepth: 0;
|
||||||
|
border-block-width: var(
|
||||||
|
--ak-m-scroll-shadows--BorderWidth,
|
||||||
|
var(--ak-m-scroll-shadows--BorderWidth--dark)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
/* #region Form controls */
|
/* #region Form controls */
|
||||||
|
|
||||||
.pf-c-form-control {
|
.pf-c-form-control {
|
||||||
|
--pf-c-form-control--BackgroundColor: transparent !important;
|
||||||
--pf-c-form-control--BorderTopColor: transparent !important;
|
--pf-c-form-control--BorderTopColor: transparent !important;
|
||||||
--pf-c-form-control--BorderRightColor: transparent !important;
|
--pf-c-form-control--BorderRightColor: transparent !important;
|
||||||
--pf-c-form-control--BorderLeftColor: transparent !important;
|
--pf-c-form-control--BorderLeftColor: transparent !important;
|
||||||
@@ -174,7 +175,9 @@ fieldset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
label.pf-c-radio {
|
label.pf-c-radio {
|
||||||
--pf-c-radio--BorderColor: var(--pf-global--BorderColor--300);
|
--pf-c-radio--checkmark--BorderColor: var(--pf-global--BorderColor--300);
|
||||||
|
--pf-c-radio--checkmark--Color: transparent;
|
||||||
|
--pf-c-radio--BorderColor: transparent;
|
||||||
--pf-c-radio--BoxShadowColor: transparent;
|
--pf-c-radio--BoxShadowColor: transparent;
|
||||||
|
|
||||||
--pf-c-radio--checked--BackgroundColor: transparent;
|
--pf-c-radio--checked--BackgroundColor: transparent;
|
||||||
@@ -182,21 +185,26 @@ label.pf-c-radio {
|
|||||||
--pf-c-radio--hover--BorderColor: var(--pf-global--active-color--200);
|
--pf-c-radio--hover--BorderColor: var(--pf-global--active-color--200);
|
||||||
|
|
||||||
--pf-c-radio--disabled--BackgroundColor: var(--pf-global--BackgroundColor--150);
|
--pf-c-radio--disabled--BackgroundColor: var(--pf-global--BackgroundColor--150);
|
||||||
|
transition:
|
||||||
|
border-color 0.2s,
|
||||||
|
color 0.2s;
|
||||||
|
|
||||||
padding-inline: var(--pf-global--spacer--md);
|
padding-inline: var(--pf-global--spacer--md);
|
||||||
padding-block: var(--pf-global--spacer--md);
|
padding-block: var(--pf-global--spacer--sm);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: var(--pf-c-radio--BackgroundColor, transparent);
|
background-color: var(--pf-c-radio--BackgroundColor, transparent);
|
||||||
border: 1px solid var(--pf-c-radio--BorderColor);
|
border: 1px solid var(--pf-c-radio--BorderColor);
|
||||||
border-radius: var(--pf-global--BorderRadius--sm);
|
border-radius: var(--pf-global--BorderRadius--sm);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
align-content: center;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"input ."
|
"input ."
|
||||||
"input .";
|
"input .";
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
column-gap: var(--pf-global--spacer--md);
|
column-gap: var(--pf-global--spacer--md);
|
||||||
|
row-gap: 0;
|
||||||
|
|
||||||
.pf-c-radio__input {
|
.pf-c-radio__input {
|
||||||
grid-area: input;
|
grid-area: input;
|
||||||
@@ -206,8 +214,17 @@ label.pf-c-radio {
|
|||||||
margin-inline-start: var(--pf-global--spacer--sm);
|
margin-inline-start: var(--pf-global--spacer--sm);
|
||||||
appearance: none;
|
appearance: none;
|
||||||
content: none;
|
content: none;
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
padding: 0.25em;
|
||||||
|
border: 1px solid;
|
||||||
|
color: var(--pf-c-radio--checkmark--BorderColor, transparent);
|
||||||
|
border-radius: 3px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
font-family: "Font Awesome 5 Free";
|
font-family: "Font Awesome 5 Free";
|
||||||
@@ -216,7 +233,8 @@ label.pf-c-radio {
|
|||||||
content: "\f00c";
|
content: "\f00c";
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: var(--pf-c-radio--checkmark--Color, transparent);
|
color: var(--pf-c-radio--checkmark--Color);
|
||||||
|
transition: color 0.2s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,14 +252,15 @@ label.pf-c-radio {
|
|||||||
inset-block-end: 0;
|
inset-block-end: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:has(.pf-c-radio__input:checked) {
|
&:has(.pf-c-radio__input:checked:not(:disabled)) {
|
||||||
--pf-c-radio--BackgroundColor: var(--pf-c-radio--checked--BackgroundColor);
|
--pf-c-radio--BackgroundColor: var(--pf-c-radio--checked--BackgroundColor);
|
||||||
--pf-c-radio--BorderColor: var(--pf-c-radio--checked--BorderColor);
|
--pf-c-radio--BorderColor: var(--pf-c-radio--checked--BorderColor);
|
||||||
--pf-c-radio--checkmark--Color: var(--pf-global--active-color--300);
|
--pf-c-radio--checkmark--BorderColor: var(--pf-global--active-color--300);
|
||||||
|
--pf-c-radio--checkmark--Color: var(--pf-c-radio--checked--BorderColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover:not(:has(.pf-c-radio__input:checked)):not(:has(.pf-c-radio__input:disabled)) {
|
||||||
--pf-c-radio--BorderColor: var(--pf-c-radio--hover--BorderColor);
|
--pf-c-radio--checkmark--BorderColor: var(--pf-c-radio--hover--BorderColor);
|
||||||
--pf-c-radio--checkmark--Color: var(--pf-c-radio--hover--BorderColor);
|
--pf-c-radio--checkmark--Color: var(--pf-c-radio--hover--BorderColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,6 +269,18 @@ label.pf-c-radio {
|
|||||||
|
|
||||||
--pf-c-radio--BackgroundColor: var(--pf-c-radio--disabled--BackgroundColor) !important;
|
--pf-c-radio--BackgroundColor: var(--pf-c-radio--disabled--BackgroundColor) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pf-c-radio__description {
|
||||||
|
margin-block-start: var(--pf-global--spacer--xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-radio__label {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.pf-c-radio__input:last-child) .pf-c-radio__label {
|
||||||
|
grid-row: 1 / -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-form__helper-radio:first-child {
|
.pf-c-form__helper-radio:first-child {
|
||||||
@@ -257,44 +288,27 @@ label.pf-c-radio {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
:has(ak-radio) {
|
|
||||||
&::part(form-group) {
|
|
||||||
grid-template-columns: [label] auto 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::part(form-group) {
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::part(group-control) {
|
|
||||||
grid-column: label / -label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::part(radio-help) {
|
::part(radio-help) {
|
||||||
padding-block-start: var(--pf-global--spacer--form-element);
|
padding-block-start: var(--pf-c-form--m-horizontal__group-label--md--PaddingTop);
|
||||||
font-size: var(--pf-c-form__label--FontSize);
|
font-size: var(--pf-c-form__label--FontSize);
|
||||||
line-height: var(--pf-global--LineHeight--md);
|
line-height: var(--pf-c-form__label--LineHeight);
|
||||||
font-style: italic;
|
|
||||||
color: var(--pf-c-form__group-label-help--Color);
|
|
||||||
text-align: end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
ak-switch-input {
|
ak-switch-input {
|
||||||
padding-inline: var(--pf-global--spacer--form-element);
|
padding-inline: var(--pf-global--spacer--form-element);
|
||||||
|
::part(group-control) {
|
||||||
::part(form-group) {
|
grid-area: control;
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-switch {
|
.pf-c-switch {
|
||||||
grid-template-columns: [label] 1fr [control] 1fr;
|
grid-template-columns: [label] auto [control] auto;
|
||||||
grid-template-rows: [primary] auto [secondary] auto;
|
grid-template-rows: [primary] auto [secondary] auto;
|
||||||
justify-content: space-between;
|
justify-content: start;
|
||||||
padding-block: var(--pf-global--spacer--xs);
|
padding-block: var(--pf-global--spacer--xs);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
column-gap: var(--pf-global--spacer--sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-switch__label {
|
.pf-c-switch__label {
|
||||||
@@ -353,8 +367,6 @@ ak-switch-input {
|
|||||||
--pf-global--link--Color: var(--pf-global--link--Color--light) !important;
|
--pf-global--link--Color: var(--pf-global--link--Color--light) !important;
|
||||||
--pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover) !important;
|
--pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover) !important;
|
||||||
|
|
||||||
--pf-c-form-control--BackgroundColor: var(--ak-dark-background-light) !important;
|
|
||||||
|
|
||||||
--pf-c-form-control--readonly--BackgroundColor: var(--ak-dark-background-light) !important;
|
--pf-c-form-control--readonly--BackgroundColor: var(--ak-dark-background-light) !important;
|
||||||
--pf-c-form-control--disabled--BackgroundColor: var(--ak-dark-background-light) !important;
|
--pf-c-form-control--disabled--BackgroundColor: var(--ak-dark-background-light) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.pf-c-select {
|
.pf-c-select {
|
||||||
|
--pf-c-select__toggle--BackgroundColor: transparent;
|
||||||
--pf-c-select__toggle--before--BorderTopColor: transparent;
|
--pf-c-select__toggle--before--BorderTopColor: transparent;
|
||||||
--pf-c-select__toggle--before--BorderRightColor: transparent;
|
--pf-c-select__toggle--before--BorderRightColor: transparent;
|
||||||
--pf-c-select__toggle--before--BorderLeftColor: transparent;
|
--pf-c-select__toggle--before--BorderLeftColor: transparent;
|
||||||
|
|||||||
@@ -179,5 +179,123 @@ test.describe("Groups", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Edit group from view page", async ({ navigator, form, pointer, page }, testInfo) => {
|
||||||
|
const groupName = groupNames.get(testInfo.testId)!;
|
||||||
|
|
||||||
|
const { fill, search } = form;
|
||||||
|
const { click } = pointer;
|
||||||
|
|
||||||
|
const newGroupDialog = page.getByRole("dialog", { name: "New Group" });
|
||||||
|
const editGroupDialog = page.getByRole("dialog", { name: "Edit Group" });
|
||||||
|
|
||||||
|
await test.step("Create group", async () => {
|
||||||
|
await click("New Group", "button");
|
||||||
|
|
||||||
|
await expect(newGroupDialog, "Dialog opens").toBeVisible();
|
||||||
|
|
||||||
|
await fill(/^Group Name/, groupName, newGroupDialog);
|
||||||
|
|
||||||
|
await newGroupDialog.getByRole("button", { name: "Create Group" }).click();
|
||||||
|
|
||||||
|
await expect(newGroupDialog, "Dialog closes after creating group").toBeHidden({
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Navigate to group view page", async () => {
|
||||||
|
const $group = await search(groupName);
|
||||||
|
|
||||||
|
await expect($group, "Group is visible").toBeVisible();
|
||||||
|
|
||||||
|
const viewLink = $group.getByRole("link", { name: "view details" });
|
||||||
|
await expect(viewLink, "View details link is visible").toBeVisible();
|
||||||
|
|
||||||
|
await viewLink.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedName = `${groupName} Edited`;
|
||||||
|
|
||||||
|
await test.step("Edit group from view page", async () => {
|
||||||
|
await expect(editGroupDialog, "Edit dialog is initially closed").toBeHidden();
|
||||||
|
|
||||||
|
await click("Edit", "button");
|
||||||
|
|
||||||
|
await expect(editGroupDialog, "Edit dialog opens").toBeVisible();
|
||||||
|
|
||||||
|
const nameInput = editGroupDialog.getByRole("textbox", { name: /Group Name/ });
|
||||||
|
|
||||||
|
await expect(nameInput, "Name input is visible").toBeVisible();
|
||||||
|
await expect(nameInput, "Name is pre-filled").toHaveValue(groupName);
|
||||||
|
|
||||||
|
await nameInput.fill(updatedName);
|
||||||
|
|
||||||
|
await editGroupDialog.getByRole("button", { name: "Save Changes" }).click();
|
||||||
|
|
||||||
|
await expect(editGroupDialog, "Edit dialog closes after saving").toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Verify group name updated on view page", async () => {
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: updatedName }).first(),
|
||||||
|
"Updated group name is visible on view page",
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Edit group from related group list", async ({
|
||||||
|
navigator,
|
||||||
|
form,
|
||||||
|
pointer,
|
||||||
|
page,
|
||||||
|
}, testInfo) => {
|
||||||
|
const groupName = groupNames.get(testInfo.testId)!;
|
||||||
|
|
||||||
|
const { fill, search } = form;
|
||||||
|
const { click } = pointer;
|
||||||
|
|
||||||
|
const newGroupDialog = page.getByRole("dialog", { name: "New Group" });
|
||||||
|
|
||||||
|
await test.step("Create group with admin user", async () => {
|
||||||
|
await click("New Group", "button");
|
||||||
|
|
||||||
|
await expect(newGroupDialog, "Dialog opens").toBeVisible();
|
||||||
|
|
||||||
|
await fill(/^Group Name/, groupName, newGroupDialog);
|
||||||
|
|
||||||
|
await newGroupDialog.getByRole("button", { name: "Create Group" }).click();
|
||||||
|
|
||||||
|
await expect(newGroupDialog, "Dialog closes").toBeHidden({ timeout: 10_000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Navigate to admin user", async () => {
|
||||||
|
await navigator.navigate("/if/admin/#/identity/users");
|
||||||
|
|
||||||
|
const $adminUser = await search(adminUsername);
|
||||||
|
|
||||||
|
await expect($adminUser, "Admin user is visible").toBeVisible();
|
||||||
|
|
||||||
|
const viewLink = $adminUser.getByRole("link", {
|
||||||
|
name: "View details for authentik Default Admin",
|
||||||
|
});
|
||||||
|
await expect(viewLink, "View details link is visible").toBeVisible();
|
||||||
|
|
||||||
|
await viewLink.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Add user to group via related group list", async () => {
|
||||||
|
await click("Groups", "tab");
|
||||||
|
|
||||||
|
const groupsPanel = page.getByRole("tabpanel", { name: "Groups" });
|
||||||
|
|
||||||
|
const addGroupDialog = page.getByRole("dialog", { name: "Add Group" });
|
||||||
|
|
||||||
|
await expect(addGroupDialog, "Add dialog is initially closed").toBeHidden();
|
||||||
|
|
||||||
|
await groupsPanel.getByRole("button", { name: "Add to existing group" }).click();
|
||||||
|
|
||||||
|
await expect(addGroupDialog, "Add dialog opens").toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -106,5 +106,67 @@ test.describe("Roles", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Edit role from view page", async ({ navigator, form, pointer, page }, testInfo) => {
|
||||||
|
const roleName = roleNames.get(testInfo.testId)!;
|
||||||
|
|
||||||
|
const { fill, search } = form;
|
||||||
|
const { click } = pointer;
|
||||||
|
|
||||||
|
const newRoleDialog = page.getByRole("dialog", { name: "New Role" });
|
||||||
|
const editRoleDialog = page.getByRole("dialog", { name: "Edit Role" });
|
||||||
|
|
||||||
|
await test.step("Create role", async () => {
|
||||||
|
await click("New Role", "button");
|
||||||
|
|
||||||
|
await expect(newRoleDialog, "Dialog opens").toBeVisible();
|
||||||
|
|
||||||
|
await fill(/^Role Name/, roleName, newRoleDialog);
|
||||||
|
|
||||||
|
await newRoleDialog.getByRole("button", { name: "Create Role" }).click();
|
||||||
|
|
||||||
|
await expect(newRoleDialog, "Dialog closes after creating role").toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Navigate to role view page", async () => {
|
||||||
|
const $role = await search(roleName);
|
||||||
|
|
||||||
|
await expect($role, "Role is visible").toBeVisible();
|
||||||
|
|
||||||
|
const viewLink = $role.getByRole("link", { name: "view details" });
|
||||||
|
await expect(viewLink, "View details link is visible").toBeVisible();
|
||||||
|
|
||||||
|
const viewURL = await viewLink.evaluate((el: HTMLAnchorElement) => el.href);
|
||||||
|
await navigator.navigate(viewURL);
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedName = `${roleName} View Edited`;
|
||||||
|
|
||||||
|
await test.step("Edit role from view page", async () => {
|
||||||
|
await expect(editRoleDialog, "Edit dialog is initially closed").toBeHidden();
|
||||||
|
|
||||||
|
await click("Edit", "button");
|
||||||
|
|
||||||
|
await expect(editRoleDialog, "Edit dialog opens").toBeVisible();
|
||||||
|
|
||||||
|
const nameInput = editRoleDialog.getByRole("textbox", { name: /Role Name/ });
|
||||||
|
|
||||||
|
await expect(nameInput, "Name input is visible").toBeVisible();
|
||||||
|
await expect(nameInput, "Name is pre-filled").toHaveValue(roleName);
|
||||||
|
|
||||||
|
await nameInput.fill(updatedName);
|
||||||
|
|
||||||
|
await editRoleDialog.getByRole("button", { name: "Save Changes" }).click();
|
||||||
|
|
||||||
|
await expect(editRoleDialog, "Edit dialog closes after saving").toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step("Verify role name updated on view page", async () => {
|
||||||
|
await expect(
|
||||||
|
page.getByText(updatedName),
|
||||||
|
"Updated role name is visible on view page",
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user