Compare commits

...

4 Commits

Author SHA1 Message Date
Teffen Ellis
1d84cc49e4 Tidy prefixing. 2026-03-05 01:59:24 +01:00
Teffen Ellis
36007c5d1c Clean up route declarations. 2026-03-05 01:27:59 +01:00
Teffen Ellis
27c846545b Fix Lit warnings. 2026-03-05 01:27:46 +01:00
Teffen Ellis
d68515f9ff Add default exports. 2026-03-05 01:22:35 +01:00
61 changed files with 466 additions and 386 deletions

View File

@@ -93,6 +93,8 @@ export class DebugPage extends AKElement {
}
}
export default DebugPage;
declare global {
interface HTMLElementTagNameMap {
"ak-admin-debug-page": DebugPage;

View File

@@ -2,201 +2,118 @@ import "#admin/admin-overview/AdminOverviewPage";
import { globalAK } from "#common/global";
import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "#elements/router/Route";
import {
ID_REGEX as id,
Route,
SLUG_REGEX as slug,
UUID_REGEX as uuid,
} from "#elements/router/Route";
import { collateRoutes, RouteEntry } from "#elements/router/shared";
import { html } from "lit";
// prettier-ignore
const routePairs: RouteEntry[] = collateRoutes([
["admin/settings", () => import("./admin-settings/AdminSettingsPage")],
["administration", [
["dashboard/users", () => import("./admin-overview/DashboardUserPage")],
["system-tasks", () => import("./admin-overview/SystemTasksPage")],
]],
["blueprints/instances", () => import("./blueprints/BlueprintListPage")],
["core", [
["providers", () => import("./providers/ProviderListPage")],
[`providers/(?<providerID>${id})`, () => import("./providers/ProviderViewPage")],
["applications", () => import("./applications/ApplicationListPage")],
[`applications/(?<applicationSlug>${slug})`, () => import("./applications/ApplicationViewPage")],
["sources", () => import("./sources/SourceListPage")],
[`sources/(?<sourceSlug>${slug})`, () => import("./sources/SourceViewPage")],
["property-mappings", () => import("./property-mappings/PropertyMappingListPage")],
["tokens", () => import("./tokens/TokenListPage")],
["brands", () => import("./brands/BrandListPage")],
]],
["crypto/certificates", () => import("./crypto/CertificateKeyPairListPage")],
["debug", () => import("./DebugPage")],
["endpoints", [
["devices", () => import("./endpoints/devices/DeviceListPage")],
[`devices/(?<deviceID>${uuid})`, () => import("./endpoints/devices/DeviceViewPage")],
["connectors", () => import("./endpoints/connectors/ConnectorsListPage")],
[`connectors/(?<connectorID>${uuid})`,() => import("./endpoints/connectors/ConnectorViewPage")],
["groups", () => import("./endpoints/DeviceAccessGroupsListPage")],
]],
["enterprise/licenses", () => import("./enterprise/EnterpriseLicenseListPage")],
["events", [
["log", () => import("./events/EventListPage")],
[`log/(?<eventID>${uuid})`, () => import("./events/EventViewPage")],
["transports", () => import("./events/TransportListPage")],
["rules", () => import("./events/RuleListPage")],
["exports", () => import("./events/DataExportListPage")],
["lifecycle-rules", () => import("./lifecycle/LifecycleRuleListPage")],
["lifecycle-reviews", () => import("./lifecycle/ReviewListPage")],
]],
["files", () => import("./files/FileListPage")],
["flow", [
["flows", () => import("./flows/FlowListPage")],
[`flows/(?<flowSlug>${slug})`, () => import("./flows/FlowViewPage")],
["stages", () => import("./stages/StageListPage")],
["stages/invitations", () => import("./stages/invitation/InvitationListPage")],
["stages/prompts", () => import("./stages/prompt/PromptListPage")],
]],
["identity", [
["groups", () => import("./groups/GroupListPage")],
[`groups/(?<groupID>${uuid})`, () => import("./groups/GroupViewPage")],
["users", () => import("./users/UserListPage")],
[`users/(?<userID>${id})`, () => import("./users/UserViewPage")],
["roles", () => import("./roles/RoleListPage")],
[`roles/(?<roleID>${uuid})`, () => import("./roles/RoleViewPage")],
["initial-permissions", () => import("./rbac/InitialPermissionsListPage")],
]],
["outpost", [
["outposts", () => import("./outposts/OutpostListPage")],
["integrations", () => import("./outposts/ServiceConnectionListPage")],
]],
["policy", [
["policies", () => import("./policies/PolicyListPage")],
["reputation", () => import("./policies/reputation/ReputationListPage")],
]],
]);
export const ROUTES: Route[] = [
// Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/administration/overview"),
new Route(new RegExp("^#.*")).redirect("/administration/overview"),
new Route(new RegExp("^/library$")).redirect("/if/user/", true),
new Route({ pattern: new RegExp("^/$") }).redirect("/administration/overview"),
new Route({ pattern: new RegExp("^#.*") }).redirect("/administration/overview"),
new Route({ pattern: "/library" }).redirect("/if/user/", true),
// statically imported since this is the default route
new Route(new RegExp("^/administration/overview$"), async () => {
return html`<ak-admin-overview></ak-admin-overview>`;
}),
new Route(new RegExp("^/administration/dashboard/users$"), async () => {
await import("#admin/admin-overview/DashboardUserPage");
return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
}),
new Route(new RegExp("^/administration/system-tasks$"), async () => {
await import("#admin/admin-overview/SystemTasksPage");
return html`<ak-system-tasks></ak-system-tasks>`;
}),
new Route(new RegExp("^/core/providers$"), async () => {
await import("#admin/providers/ProviderListPage");
return html`<ak-provider-list></ak-provider-list>`;
}),
new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`), async (args) => {
await import("#admin/providers/ProviderViewPage");
return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`;
}),
new Route(new RegExp("^/core/applications$"), async () => {
await import("#admin/applications/ApplicationListPage");
return html`<ak-application-list></ak-application-list>`;
}),
new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`), async (args) => {
await import("#admin/applications/ApplicationViewPage");
return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`;
}),
new Route(new RegExp("^/endpoints/devices$"), async () => {
await import("#admin/endpoints/devices/DeviceListPage");
return html`<ak-endpoints-device-list></ak-endpoints-device-list>`;
}),
new Route(new RegExp(`^/endpoints/devices/(?<uuid>${UUID_REGEX})$`), async (args) => {
await import("#admin/endpoints/devices/DeviceViewPage");
return html`<ak-endpoints-device-view .deviceId=${args.uuid}></ak-endpoints-device-view>`;
}),
new Route(new RegExp("^/endpoints/connectors$"), async () => {
await import("#admin/endpoints/connectors/ConnectorsListPage");
return html`<ak-endpoints-connectors-list></ak-endpoints-connectors-list>`;
}),
new Route(new RegExp(`^/endpoints/connectors/(?<uuid>${UUID_REGEX})$`), async (args) => {
await import("#admin/endpoints/connectors/ConnectorViewPage");
return html`<ak-endpoints-connector-view
.connectorID=${args.uuid}
></ak-endpoints-connector-view>`;
}),
new Route(new RegExp("^/endpoints/groups$"), async () => {
await import("#admin/endpoints/DeviceAccessGroupsListPage");
return html`<ak-endpoints-device-access-groups-list></ak-endpoints-device-access-groups-list>`;
}),
new Route(new RegExp("^/core/sources$"), async () => {
await import("#admin/sources/SourceListPage");
return html`<ak-source-list></ak-source-list>`;
}),
new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`), async (args) => {
await import("#admin/sources/SourceViewPage");
return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`;
}),
new Route(new RegExp("^/core/property-mappings$"), async () => {
await import("#admin/property-mappings/PropertyMappingListPage");
return html`<ak-property-mapping-list></ak-property-mapping-list>`;
}),
new Route(new RegExp("^/core/tokens$"), async () => {
await import("#admin/tokens/TokenListPage");
return html`<ak-token-list></ak-token-list>`;
}),
new Route(new RegExp("^/core/brands"), async () => {
await import("#admin/brands/BrandListPage");
return html`<ak-brand-list></ak-brand-list>`;
}),
new Route(new RegExp("^/policy/policies$"), async () => {
await import("#admin/policies/PolicyListPage");
return html`<ak-policy-list></ak-policy-list>`;
}),
new Route(new RegExp("^/policy/reputation$"), async () => {
await import("#admin/policies/reputation/ReputationListPage");
return html`<ak-policy-reputation-list></ak-policy-reputation-list>`;
}),
new Route(new RegExp("^/identity/groups$"), async () => {
await import("#admin/groups/GroupListPage");
return html`<ak-group-list></ak-group-list>`;
}),
new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`), async (args) => {
await import("#admin/groups/GroupViewPage");
return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`;
}),
new Route(new RegExp("^/identity/users$"), async () => {
await import("#admin/users/UserListPage");
return html`<ak-user-list></ak-user-list>`;
}),
new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`), async (args) => {
await import("#admin/users/UserViewPage");
return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`;
}),
new Route(new RegExp("^/identity/roles$"), async () => {
await import("#admin/roles/RoleListPage");
return html`<ak-role-list></ak-role-list>`;
}),
new Route(new RegExp("^/identity/initial-permissions$"), async () => {
await import("#admin/rbac/InitialPermissionsListPage");
return html`<ak-initial-permissions-list></ak-initial-permissions-list>`;
}),
new Route(new RegExp(`^/identity/roles/(?<id>${UUID_REGEX})$`), async (args) => {
await import("#admin/roles/RoleViewPage");
return html`<ak-role-view roleId=${args.id}></ak-role-view>`;
}),
new Route(new RegExp("^/flow/stages/invitations$"), async () => {
await import("#admin/stages/invitation/InvitationListPage");
return html`<ak-stage-invitation-list></ak-stage-invitation-list>`;
}),
new Route(new RegExp("^/flow/stages/prompts$"), async () => {
await import("#admin/stages/prompt/PromptListPage");
return html`<ak-stage-prompt-list></ak-stage-prompt-list>`;
}),
new Route(new RegExp("^/flow/stages$"), async () => {
await import("#admin/stages/StageListPage");
return html`<ak-stage-list></ak-stage-list>`;
}),
new Route(new RegExp("^/flow/flows$"), async () => {
await import("#admin/flows/FlowListPage");
return html`<ak-flow-list></ak-flow-list>`;
}),
new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`), async (args) => {
await import("#admin/flows/FlowViewPage");
return html`<ak-flow-view .flowSlug=${args.slug} exportparts="main, tabs"></ak-flow-view>`;
}),
new Route(new RegExp("^/events/log$"), async () => {
await import("#admin/events/EventListPage");
return html`<ak-event-list></ak-event-list>`;
}),
new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`), async (args) => {
await import("#admin/events/EventViewPage");
return html`<ak-event-view .eventID=${args.id}></ak-event-view>`;
}),
new Route(new RegExp("^/events/transports$"), async () => {
await import("#admin/events/TransportListPage");
return html`<ak-event-transport-list></ak-event-transport-list>`;
}),
new Route(new RegExp("^/events/rules$"), async () => {
await import("#admin/events/RuleListPage");
return html`<ak-event-rule-list></ak-event-rule-list>`;
}),
new Route(new RegExp("^/events/exports"), async () => {
await import("./events/DataExportListPage");
return html`<ak-data-export-list></ak-data-export-list>`;
}),
new Route(new RegExp("^/events/lifecycle-rules$"), async () => {
await import("#admin/lifecycle/LifecycleRuleListPage");
return html`<ak-lifecycle-rule-list></ak-lifecycle-rule-list>`;
}),
new Route(new RegExp("^/events/lifecycle-reviews"), async () => {
await import("#admin/lifecycle/ReviewListPage");
return html`<ak-review-list></ak-review-list>`;
}),
new Route(new RegExp("^/outpost/outposts$"), async () => {
await import("#admin/outposts/OutpostListPage");
return html`<ak-outpost-list></ak-outpost-list>`;
}),
new Route(new RegExp("^/outpost/integrations$"), async () => {
await import("#admin/outposts/ServiceConnectionListPage");
return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`;
}),
new Route(new RegExp("^/crypto/certificates$"), async () => {
await import("#admin/crypto/CertificateKeyPairListPage");
return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`;
}),
new Route(new RegExp("^/admin/settings$"), async () => {
await import("#admin/admin-settings/AdminSettingsPage");
return html`<ak-admin-settings></ak-admin-settings>`;
}),
new Route(new RegExp("^/files$"), async () => {
await import("#admin/files/FileListPage");
return html`<ak-files-list></ak-files-list>`;
}),
new Route(new RegExp("^/blueprints/instances$"), async () => {
await import("#admin/blueprints/BlueprintListPage");
return html`<ak-blueprint-list></ak-blueprint-list>`;
}),
new Route(new RegExp("^/debug$"), async () => {
await import("#admin/DebugPage");
return html`<ak-admin-debug-page></ak-admin-debug-page>`;
}),
new Route(new RegExp("^/enterprise/licenses$"), async () => {
await import("#admin/enterprise/EnterpriseLicenseListPage");
return html`<ak-enterprise-license-list></ak-enterprise-license-list>`;
new Route({
pattern: "/administration/overview",
handler: () => html`<ak-admin-overview></ak-admin-overview>`,
}),
...routePairs.map(([pattern, loader, handler]) => new Route({ pattern, loader, handler })),
];
/**

View File

@@ -93,6 +93,8 @@ export class DashboardUserPage extends AKElement {
}
}
export default DashboardUserPage;
declare global {
interface HTMLElementTagNameMap {
"ak-admin-dashboard-users": DashboardUserPage;

View File

@@ -74,6 +74,8 @@ export class SystemTasksPage extends AKElement {
}
}
export default SystemTasksPage;
declare global {
interface HTMLElementTagNameMap {
"ak-system-tasks": SystemTasksPage;

View File

@@ -91,6 +91,8 @@ export class AdminSettingsPage extends AKElement {
}
}
export default AdminSettingsPage;
declare global {
interface HTMLElementTagNameMap {
"ak-admin-settings": AdminSettingsPage;

View File

@@ -206,6 +206,8 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
}
}
export default ApplicationListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-application-list": ApplicationListPage;

View File

@@ -438,6 +438,8 @@ export class ApplicationViewPage extends WithLicenseSummary(AKElement) {
}
}
export default ApplicationViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-application-view": ApplicationViewPage;

View File

@@ -213,6 +213,8 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
}
}
export default BlueprintListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-blueprint-list": BlueprintListPage;

View File

@@ -110,6 +110,8 @@ export class BrandListPage extends TablePage<Brand> {
}
}
export default BrandListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-brand-list": BrandListPage;

View File

@@ -213,6 +213,8 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
}
}
export default CertificateKeyPairListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-crypto-certificate-list": CertificateKeyPairListPage;

View File

@@ -99,6 +99,8 @@ export class DeviceAccessGroupsListPage extends TablePage<DeviceAccessGroup> {
}
}
export default DeviceAccessGroupsListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-endpoints-device-access-groups-list": DeviceAccessGroupsListPage;

View File

@@ -19,7 +19,11 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
@customElement("ak-endpoints-connector-view")
export class ConnectorViewPage extends AKElement {
@property({ type: String })
@property({ type: String, attribute: "connector-id" })
get connectorID() {
return this.connector?.connectorUuid || "";
}
set connectorID(value: string) {
new EndpointsApi(DEFAULT_CONFIG)
.endpointsConnectorsRetrieve({
@@ -61,6 +65,8 @@ export class ConnectorViewPage extends AKElement {
}
}
export default ConnectorViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-endpoints-connector-view": ConnectorViewPage;

View File

@@ -95,6 +95,8 @@ export class ConnectorsListPage extends TablePage<Connector> {
}
}
export default ConnectorsListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-endpoints-connectors-list": ConnectorsListPage;

View File

@@ -171,6 +171,8 @@ export class DeviceListPage extends TablePage<EndpointDevice> {
}
}
export default DeviceListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-endpoints-device-list": DeviceListPage;

View File

@@ -34,8 +34,8 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
@customElement("ak-endpoints-device-view")
export class DeviceViewPage extends AKElement {
@property({ type: String })
public deviceId?: string;
@property({ type: String, attribute: "device-id" })
public deviceID?: string;
@state()
protected device?: EndpointDeviceDetails;
@@ -57,8 +57,8 @@ export class DeviceViewPage extends AKElement {
}
public override willUpdate(changedProperties: PropertyValues<this>) {
if (changedProperties.has("deviceId") && this.deviceId) {
this.fetchDevice(this.deviceId);
if (changedProperties.has("deviceID") && this.deviceID) {
this.fetchDevice(this.deviceID);
}
}
@@ -329,6 +329,8 @@ export class DeviceViewPage extends AKElement {
}
}
export default DeviceViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-endpoints-device-view": DeviceViewPage;

View File

@@ -289,6 +289,8 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
}
}
export default EnterpriseLicenseListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-enterprise-license-list": EnterpriseLicenseListPage;

View File

@@ -135,6 +135,8 @@ export class DataExportListPage extends TablePage<DataExport> {
}
}
export default DataExportListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-data-export-list": DataExportListPage;

View File

@@ -131,6 +131,8 @@ export class EventListPage extends WithLicenseSummary(TablePage<Event>) {
}
}
export default EventListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-event-list": EventListPage;

View File

@@ -145,6 +145,8 @@ export class EventViewPage extends AKElement {
}
}
export default EventViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-event-view": EventViewPage;

View File

@@ -16,12 +16,16 @@ export class EventVolumeChart extends EventChart {
@property({ attribute: "with-map", type: Boolean })
withMap = false;
_query?: EventsEventsListRequest;
#query?: EventsEventsListRequest;
@property({ attribute: false })
get query() {
return this.#query;
}
set query(value: EventsEventsListRequest | undefined) {
if (JSON.stringify(value) === JSON.stringify(this._query)) return;
this._query = value;
if (JSON.stringify(value) === JSON.stringify(this.#query)) return;
this.#query = value;
this.refreshHandler();
}
@@ -41,7 +45,7 @@ export class EventVolumeChart extends EventChart {
apiRequest(): Promise<EventVolume[]> {
return new EventsApi(DEFAULT_CONFIG).eventsEventsVolumeList({
historyDays: 7,
...this._query,
...this.#query,
});
}

View File

@@ -147,6 +147,8 @@ Bindings to groups/users are checked against the user of the event.`,
}
}
export default RuleListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-event-rule-list": RuleListPage;

View File

@@ -144,6 +144,8 @@ export class TransportListPage extends TablePage<NotificationTransport> {
}
}
export default TransportListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-event-transport-list": TransportListPage;

View File

@@ -161,6 +161,8 @@ export class FileListPage extends WithCapabilitiesConfig(TablePage<FileItem>) {
}
}
export default FileListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-files-list": FileListPage;

View File

@@ -182,6 +182,8 @@ export class FlowListPage extends TablePage<Flow> {
}
}
export default FlowListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-flow-list": FlowListPage;

View File

@@ -324,6 +324,8 @@ export class FlowViewPage extends AKElement {
}
}
export default FlowViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-flow-view": FlowViewPage;

View File

@@ -106,6 +106,8 @@ export class GroupListPage extends TablePage<Group> {
}
}
export default GroupListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-group-list": GroupListPage;

View File

@@ -45,8 +45,12 @@ import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";
@customElement("ak-group-view")
export class GroupViewPage extends WithLicenseSummary(AKElement) {
@property({ type: String })
set groupId(id: string) {
@property({ type: String, attribute: "group-id" })
public get groupID() {
return this.group?.pk || "";
}
public set groupID(id: string) {
new CoreApi(DEFAULT_CONFIG)
.coreGroupsRetrieve({
groupUuid: id,
@@ -77,7 +81,7 @@ export class GroupViewPage extends WithLicenseSummary(AKElement) {
super();
this.addEventListener(EVENT_REFRESH, () => {
if (!this.group?.pk) return;
this.groupId = this.group?.pk;
this.groupID = this.group?.pk;
});
}
@@ -325,6 +329,8 @@ export class GroupViewPage extends WithLicenseSummary(AKElement) {
}
}
export default GroupViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-group-view": GroupViewPage;

View File

@@ -141,6 +141,8 @@ export class LifecycleRuleListPage extends TablePage<LifecycleRule> {
}
}
export default LifecycleRuleListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-lifecycle-rule-list": LifecycleRuleListPage;

View File

@@ -84,6 +84,8 @@ export class ReviewListPage extends TablePage<LifecycleIteration> {
}
}
export default ReviewListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-review-list": ReviewListPage;

View File

@@ -242,6 +242,8 @@ export class OutpostListPage extends TablePage<Outpost> {
}
}
export default OutpostListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-outpost-list": OutpostListPage;

View File

@@ -167,6 +167,8 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
}
}
export default OutpostServiceConnectionListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-outpost-service-connection-list": OutpostServiceConnectionListPage;

View File

@@ -146,6 +146,8 @@ export class PolicyListPage extends TablePage<Policy> {
}
}
export default PolicyListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-policy-list": PolicyListPage;

View File

@@ -97,6 +97,8 @@ export class ReputationListPage extends TablePage<Reputation> {
}
}
export default ReputationListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-policy-reputation-list": ReputationListPage;

View File

@@ -156,6 +156,8 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
}
}
export default PropertyMappingListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-property-mapping-list": PropertyMappingListPage;

View File

@@ -142,6 +142,8 @@ export class ProviderListPage extends TablePage<Provider> {
}
}
export default ProviderListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-provider-list": ProviderListPage;

View File

@@ -29,11 +29,15 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
@customElement("ak-provider-view")
export class ProviderViewPage extends AKElement {
@property({ type: Number })
set providerID(value: number) {
@property({ type: Number, attribute: "provider-id" })
public get providerID() {
return this.provider?.pk || "";
}
public set providerID(value: number | string) {
new ProvidersApi(DEFAULT_CONFIG)
.providersAllRetrieve({
id: value,
id: typeof value === "string" ? parseInt(value, 10) : value,
})
.then((prov) => (this.provider = prov));
}
@@ -104,6 +108,8 @@ export class ProviderViewPage extends AKElement {
}
}
export default ProviderViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-provider-view": ProviderViewPage;

View File

@@ -65,7 +65,11 @@ export function TypeToLabel(type?: ClientTypeEnum): string {
@customElement("ak-provider-oauth2-view")
export class OAuth2ProviderViewPage extends AKElement {
@property({ type: Number })
set providerID(value: number) {
public get providerID() {
return this.provider?.pk ?? -1;
}
public set providerID(value: number) {
new ProvidersApi(DEFAULT_CONFIG)
.providersOauth2Retrieve({
id: value,

View File

@@ -110,11 +110,7 @@ export class InitialPermissionsListPage extends TablePage<InitialPermissions> {
}
}
declare global {
interface HTMLElementTagNameMap {
"initial-permissions-list": InitialPermissionsListPage;
}
}
export default InitialPermissionsListPage;
declare global {
interface HTMLElementTagNameMap {

View File

@@ -146,6 +146,8 @@ export class RoleListPage extends TablePage<Role> {
}
}
export default RoleListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-role-list": RoleListPage;

View File

@@ -38,11 +38,15 @@ import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
@customElement("ak-role-view")
export class RoleViewPage extends WithLicenseSummary(AKElement) {
@property({ type: String })
set roleId(id: string) {
@property({ type: String, attribute: "role-id" })
public get roleID() {
return this.targetRole?.pk || "";
}
public set roleID(uuid: string) {
new RbacApi(DEFAULT_CONFIG)
.rbacRolesRetrieve({
uuid: id,
uuid,
})
.then((role) => {
this.targetRole = role;
@@ -75,7 +79,7 @@ export class RoleViewPage extends WithLicenseSummary(AKElement) {
super();
this.addEventListener(EVENT_REFRESH, () => {
if (!this.targetRole?.pk) return;
this.roleId = this.targetRole?.pk;
this.roleID = this.targetRole?.pk;
});
}
@@ -182,6 +186,8 @@ export class RoleViewPage extends WithLicenseSummary(AKElement) {
}
}
export default RoleViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-role-view": RoleViewPage;

View File

@@ -124,6 +124,8 @@ export class SourceListPage extends TablePage<Source> {
}
}
export default SourceListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-source-list": SourceListPage;

View File

@@ -11,66 +11,67 @@ import "#elements/buttons/SpinnerButton/ak-spinner-button";
import { DEFAULT_CONFIG } from "#common/api/config";
import { AKElement } from "#elements/Base";
import { SlottedTemplateResult } from "#elements/types";
import { StrictUnsafe } from "#elements/utils/unsafe";
import { setPageDetails } from "#components/ak-page-navbar";
import { Source, SourcesApi } from "@goauthentik/api";
import { html, PropertyValues, TemplateResult } from "lit";
import { html, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
function resolveSourceViewComponentName(component: string) {
switch (component) {
case "ak-source-kerberos-form":
return "ak-source-kerberos-view";
case "ak-source-ldap-form":
return "ak-source-ldap-view";
case "ak-source-oauth-form":
return "ak-source-oauth-view";
case "ak-source-saml-form":
return "ak-source-saml-view";
case "ak-source-plex-form":
return "ak-source-plex-view";
case "ak-source-scim-form":
return "ak-source-scim-view";
case "ak-source-telegram-form":
return "ak-source-telegram-view";
default:
return null;
}
}
@customElement("ak-source-view")
export class SourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesAllRetrieve({
slug: slug,
})
.then((source) => {
this.source = source;
});
@property({ type: String, attribute: "source-slug" })
public get sourceSlug() {
return this.source?.slug || "";
}
public set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesAllRetrieve({ slug }).then((source) => {
this.source = source;
});
}
@property({ attribute: false })
source?: Source;
render(): TemplateResult {
render(): SlottedTemplateResult {
if (!this.source) {
return html`<ak-empty-state loading full-height></ak-empty-state>`;
}
switch (this.source?.component) {
case "ak-source-kerberos-form":
return html`<ak-source-kerberos-view
sourceSlug=${this.source.slug}
></ak-source-kerberos-view>`;
case "ak-source-ldap-form":
return html`<ak-source-ldap-view
sourceSlug=${this.source.slug}
></ak-source-ldap-view>`;
case "ak-source-oauth-form":
return html`<ak-source-oauth-view
sourceSlug=${this.source.slug}
></ak-source-oauth-view>`;
case "ak-source-saml-form":
return html`<ak-source-saml-view
sourceSlug=${this.source.slug}
></ak-source-saml-view>`;
case "ak-source-plex-form":
return html`<ak-source-plex-view
sourceSlug=${this.source.slug}
></ak-source-plex-view>`;
case "ak-source-scim-form":
return html`<ak-source-scim-view
sourceSlug=${this.source.slug}
></ak-source-scim-view>`;
case "ak-source-telegram-form":
return html`<ak-source-telegram-view
sourceSlug=${this.source.slug}
></ak-source-telegram-view>`;
default:
return html`<p>Invalid source type ${this.source.component}</p>`;
const sourceViewComponentName = resolveSourceViewComponentName(this.source.component);
if (!sourceViewComponentName) {
return html`<p>Invalid source type ${this.source.component}</p>`;
}
return StrictUnsafe(sourceViewComponentName, {
sourceSlug: this.source.slug,
});
}
updated(changed: PropertyValues<this>) {
@@ -83,6 +84,8 @@ export class SourceViewPage extends AKElement {
}
}
export default SourceViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-source-view": SourceViewPage;

View File

@@ -41,15 +41,15 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
@customElement("ak-source-kerberos-view")
export class KerberosSourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesKerberosRetrieve({
slug: slug,
})
.then((source) => {
this.source = source;
});
@property({ type: String, attribute: "source-slug" })
public get sourceSlug() {
return this.source?.slug || "";
}
public set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesKerberosRetrieve({ slug }).then((source) => {
this.source = source;
});
}
@property({ attribute: false })

View File

@@ -37,15 +37,15 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
@customElement("ak-source-ldap-view")
export class LDAPSourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesLdapRetrieve({
slug: slug,
})
.then((source) => {
this.source = source;
});
@property({ type: String, attribute: "source-slug" })
public get sourceSlug() {
return this.source?.slug || "";
}
public set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesLdapRetrieve({ slug }).then((source) => {
this.source = source;
});
}
@property({ attribute: false })

View File

@@ -79,15 +79,15 @@ export function ProviderToLabel(provider?: ProviderTypeEnum): string {
@customElement("ak-source-oauth-view")
export class OAuthSourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(value: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthRetrieve({
slug: value,
})
.then((source) => {
this.source = source;
});
@property({ type: String, attribute: "source-slug" })
public get sourceSlug() {
return this.source?.slug || "";
}
public set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesOauthRetrieve({ slug }).then((source) => {
this.source = source;
});
}
@property({ attribute: false })

View File

@@ -34,15 +34,15 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
@customElement("ak-source-plex-view")
export class PlexSourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(value: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesPlexRetrieve({
slug: value,
})
.then((source) => {
this.source = source;
});
@property({ type: String, attribute: "source-slug" })
public get sourceSlug() {
return this.source?.slug || "";
}
public set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesPlexRetrieve({ slug }).then((source) => {
this.source = source;
});
}
@property({ attribute: false })

View File

@@ -36,15 +36,15 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
@customElement("ak-source-saml-view")
export class SAMLSourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesSamlRetrieve({
slug: slug,
})
.then((source) => {
this.source = source;
});
@property({ type: String, attribute: "source-slug" })
public get sourceSlug() {
return this.source?.slug || "";
}
public set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesSamlRetrieve({ slug }).then((source) => {
this.source = source;
});
}
@property({ attribute: false })

View File

@@ -36,15 +36,15 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
@customElement("ak-source-scim-view")
export class SCIMSourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(value: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesScimRetrieve({
slug: value,
})
.then((source) => {
this.source = source;
});
@property({ type: String, attribute: "source-slug" })
public get sourceSlug() {
return this.source?.slug || "";
}
public set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesScimRetrieve({ slug }).then((source) => {
this.source = source;
});
}
@property({ attribute: false })

View File

@@ -30,15 +30,15 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
@customElement("ak-source-telegram-view")
export class TelegramSourceViewPage extends AKElement {
@property({ type: String })
set sourceSlug(value: string) {
new SourcesApi(DEFAULT_CONFIG)
.sourcesTelegramRetrieve({
slug: value,
})
.then((source) => {
this.source = source;
});
@property({ type: String, attribute: "source-slug" })
public get sourceSlug() {
return this.source?.slug || "";
}
public set sourceSlug(slug: string) {
new SourcesApi(DEFAULT_CONFIG).sourcesTelegramRetrieve({ slug }).then((source) => {
this.source = source;
});
}
@property({ attribute: false })

View File

@@ -126,6 +126,8 @@ export class StageListPage extends TablePage<Stage> {
}
}
export default StageListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-stage-list": StageListPage;

View File

@@ -184,6 +184,8 @@ export class InvitationListPage extends TablePage<Invitation> {
}
}
export default InvitationListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-stage-invitation-list": InvitationListPage;

View File

@@ -108,6 +108,8 @@ export class PromptListPage extends TablePage<Prompt> {
}
}
export default PromptListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-stage-prompt-list": PromptListPage;

View File

@@ -141,6 +141,8 @@ export class TokenListPage extends TablePage<Token> {
}
}
export default TokenListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-token-list": TokenListPage;

View File

@@ -429,6 +429,8 @@ export class UserListPage extends WithBrandConfig(
}
}
export default UserListPage;
declare global {
interface HTMLElementTagNameMap {
"ak-user-list": UserListPage;

View File

@@ -66,11 +66,15 @@ import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";
@customElement("ak-user-view")
export class UserViewPage extends WithBrandConfig(WithCapabilitiesConfig(WithSession(AKElement))) {
@property({ type: Number })
set userId(id: number) {
@property({ type: Number, attribute: "user-id" })
public get userID() {
return this.user?.pk || "";
}
public set userID(id: string | number) {
new CoreApi(DEFAULT_CONFIG)
.coreUsersRetrieve({
id: id,
id: typeof id === "string" ? parseInt(id, 10) : id,
})
.then((user) => {
this.user = user;
@@ -517,6 +521,8 @@ export class UserViewPage extends WithBrandConfig(WithCapabilitiesConfig(WithSes
}
}
export default UserViewPage;
declare global {
interface HTMLElementTagNameMap {
"ak-user-view": UserViewPage;

View File

@@ -1,31 +1,33 @@
import "#elements/EmptyState";
import { SlottedTemplateResult } from "#elements/types";
import { assertDefaultExport, ImportCallback } from "#common/modules/types";
import { html, nothing, TemplateResult } from "lit";
import { RouteHandler, RouteInit, RouteLoader, RouteParameters } from "#elements/router/shared";
import { SlottedTemplateResult } from "#elements/types";
import { StrictUnsafe } from "#elements/utils/unsafe";
import { html, nothing } from "lit";
import { until } from "lit/directives/until.js";
export const SLUG_REGEX = "[-a-zA-Z0-9_]+";
export const ID_REGEX = "\\d+";
export const UUID_REGEX = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
export interface RouteArgs {
[key: string]: string;
}
export class Route {
url: RegExp;
public readonly pattern: RegExp;
private element?: TemplateResult;
private callback?: (args: RouteArgs) => Promise<SlottedTemplateResult>;
#loader: RouteLoader | ImportCallback<object> | null;
#handler: RouteHandler | null = null;
constructor(url: RegExp, callback?: (args: RouteArgs) => Promise<TemplateResult>) {
this.url = url;
this.callback = callback;
constructor({ pattern, loader, handler }: RouteInit) {
this.pattern = typeof pattern === "string" ? new RegExp(`^${pattern}$`) : pattern;
this.#loader = loader || null;
this.#handler = handler || null;
}
redirect(to: string, raw = false): Route {
this.callback = async () => {
public redirect(to: string, raw = false): Route {
this.#handler = async () => {
console.debug(`authentik/router: redirecting ${to}`);
if (!raw) {
window.location.hash = `#${to}`;
@@ -37,32 +39,42 @@ export class Route {
return this;
}
then(render: (args: RouteArgs) => TemplateResult): Route {
this.callback = async (args) => {
return render(args);
public render(params: RouteParameters): SlottedTemplateResult {
const invoke = (mod?: unknown) => {
if (this.#handler) {
return this.#handler(params);
}
if (!mod) {
throw new TypeError(
"Route moduled did not provide a callback or load a module with a default export",
);
}
assertDefaultExport<CustomElementConstructor>(mod);
const tagName = window.customElements.getName(mod.default);
if (!tagName) {
throw new TypeError(
"Route provided a module that did not register a custom element",
);
}
return StrictUnsafe(tagName, params);
};
return this;
}
thenAsync(render: (args: RouteArgs) => Promise<TemplateResult>): Route {
this.callback = render;
return this;
}
render(args: RouteArgs): TemplateResult {
if (this.callback) {
return html`${until(
this.callback(args),
if (this.#loader) {
return until(
this.#loader().then(invoke),
html`<ak-empty-state loading></ak-empty-state>`,
)}`;
);
}
if (this.element) {
return this.element;
}
throw new Error("Route does not have callback or element");
return invoke();
}
toString(): string {
return `<Route url=${this.url} callback=${this.callback ? "true" : "false"}>`;
public toString(): string {
return `<Route url=${this.pattern} callback=${this.#handler ? "true" : "false"}>`;
}
}

View File

@@ -2,22 +2,21 @@ import { ROUTE_SEPARATOR } from "#common/constants";
import { Route } from "#elements/router/Route";
import { RouteParameterRecord } from "#elements/router/shared";
import { TemplateResult } from "lit";
import { SlottedTemplateResult } from "#elements/types";
export class RouteMatch {
route: Route;
arguments: { [key: string]: string };
params: Record<string, string>;
fullURL: string;
constructor(route: Route, fullUrl: string) {
constructor(route: Route, fullURL: string) {
this.route = route;
this.arguments = {};
this.fullURL = fullUrl;
this.params = {};
this.fullURL = fullURL;
}
render(): TemplateResult {
return this.route.render(this.arguments);
render(): SlottedTemplateResult {
return this.route.render(this.params);
}
/**
@@ -26,18 +25,18 @@ export class RouteMatch {
*
* @returns The sanitized URL for logging/tracing.
*/
sanitizedURL() {
sanitizedURL(): string {
let cleanedURL = this.fullURL;
for (const match of Object.keys(this.arguments)) {
const value = this.arguments[match];
cleanedURL = cleanedURL?.replace(value, `:${match}`);
for (const match of Object.keys(this.params)) {
const value = this.params[match];
cleanedURL = cleanedURL.replace(value, `:${match}`);
}
return cleanedURL;
}
toString(): string {
return `<RouteMatch url=${this.sanitizedURL()} route=${this.route} arguments=${JSON.stringify(
this.arguments,
return `<RouteMatch url=${this.sanitizedURL()} route=${this.route} params=${JSON.stringify(
this.params,
)}>`;
}
}

View File

@@ -8,6 +8,7 @@ import { AKElement } from "#elements/Base";
import { RouteChangeEvent } from "#elements/router/events";
import { Route } from "#elements/router/Route";
import { RouteMatch } from "#elements/router/RouteMatch";
import { SlottedTemplateResult } from "#elements/types";
import { ifPreviousValue, onlyBinding } from "#elements/utils/properties";
import { ConsoleLogger } from "#logger/browser";
@@ -21,7 +22,7 @@ import {
} from "@sentry/browser";
import { BaseTransportOptions, Client, ClientOptions } from "@sentry/core";
import { html, PropertyValues, TemplateResult } from "lit";
import { html, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
// Polyfill for hashchange.newURL,
@@ -167,11 +168,11 @@ export class RouterOutlet extends AKElement {
let matchedRoute: RouteMatch | null = null;
for (const route of this.routes) {
const match = route.url.exec(activeUrl);
const match = route.pattern.exec(activeUrl);
if (match !== null) {
matchedRoute = new RouteMatch(route, activeUrl);
matchedRoute.arguments = match.groups || {};
matchedRoute.params = match.groups || {};
this.#logger.debug(matchedRoute);
@@ -181,13 +182,15 @@ export class RouterOutlet extends AKElement {
if (!matchedRoute) {
this.#logger.info(`Route "${activeUrl}" not defined`);
const route = new Route(RegExp(""), async () => {
return html`<div class="pf-c-page__main">
<ak-router-404 url=${activeUrl}></ak-router-404>
</div>`;
const route = new Route({
pattern: /.*/,
handler: () =>
html`<div class="pf-c-page__main">
<ak-router-404 url=${activeUrl}></ak-router-404>
</div>`,
});
matchedRoute = new RouteMatch(route, activeUrl);
matchedRoute.arguments = route.url.exec(activeUrl)?.groups || {};
matchedRoute.params = route.pattern.exec(activeUrl)?.groups || {};
}
this.current = matchedRoute;
@@ -214,8 +217,8 @@ export class RouterOutlet extends AKElement {
}
}
render(): TemplateResult | undefined {
return this.current?.render();
protected override render(): SlottedTemplateResult {
return this.current?.render() || null;
}
}

View File

@@ -2,5 +2,39 @@
* @file Common types for routing.
*/
import { DefaultImportCallback, ImportCallback } from "#common/modules/types";
import { SlottedTemplateResult } from "#elements/types";
export type PrimitiveRouteParameter = string | number | boolean | null | undefined;
export type RouteParameterRecord = { [key: string]: PrimitiveRouteParameter };
export type RouteParameters = Record<string, string>;
export type RouteLoader = DefaultImportCallback<CustomElementConstructor>;
export type RouteHandler<P extends object = RouteParameters> = (
parameters: P,
) => SlottedTemplateResult;
export interface RouteInit<P extends object = RouteParameters> {
pattern: RegExp | string;
loader?: RouteLoader | ImportCallback<object>;
handler?: RouteHandler<P>;
}
export type RouteEntry =
| [pattern: RegExp | string, loader: ImportCallback<object>, handler: RouteHandler]
| [pattern: RegExp | string, loader: RouteLoader, handler?: RouteHandler];
type RouteGroup = [prefix: string, children: NestedRouteEntry[]];
type NestedRouteEntry = RouteEntry | RouteGroup;
export function collateRoutes(entries: NestedRouteEntry[], prefix = ""): RouteEntry[] {
return entries.flatMap((entry) => {
const [segment, second] = entry;
if (Array.isArray(second)) {
return collateRoutes(second, `${prefix}/${segment}`);
}
return [[`${prefix}/${segment}`, ...entry.slice(1)] as RouteEntry];
});
}

View File

@@ -6,11 +6,11 @@ import { html } from "lit";
export const ROUTES: Route[] = [
// Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/library"),
new Route(new RegExp("^#.*")).redirect("/library"),
new Route(new RegExp("^/library$"), async () => html`<ak-library></ak-library>`),
new Route(new RegExp("^/settings$"), async () => {
await import("#user/user-settings/UserSettingsPage");
return html`<ak-user-settings></ak-user-settings>`;
new Route({ pattern: new RegExp("^/$") }).redirect("/library"),
new Route({ pattern: new RegExp("^#.*") }).redirect("/library"),
new Route({ pattern: "/library", handler: () => html`<ak-library></ak-library>` }),
new Route({
pattern: "/settings",
loader: () => import("#user/user-settings/UserSettingsPage"),
}),
];

View File

@@ -189,6 +189,8 @@ export class UserSettingsPage extends WithSession(AKElement) {
}
}
export default UserSettingsPage;
declare global {
interface HTMLElementTagNameMap {
"ak-user-settings": UserSettingsPage;