From a0d2aca61c32b93b3e1e2699f22b60a7c773c6f4 Mon Sep 17 00:00:00 2001 From: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:51:42 -0700 Subject: [PATCH] web: detangle components from applications (#6891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Web: Detangling some circular dependencies in Admin and User Admin, User, and Flow should not dependend upon each other, at least not in a circular way. If Admin and User depend on Flow, that's fine, but Flow should not correspondingly depend upon elements of either; if they have something in common, let's put them in `@goauthentik/common` or find some other smart place to store them. This commit refactors the intentToLabel and actionToLabel functions into `@goauthentik/common/labels` and converts them to static tables for maintenance purposes. * web: "Consistency is the hobgoblin of small minds" - Ralph Waldo Emerson * web: I found these confusing to look at, so I added comments. * web: remove admin-to-user component reference(s) There was only one: AppIcon. This has been moved to `components`. Touching the LibraryApplications page triggered a cyclomatic complexity check. Extracting the expansion block and streamlining the class and style declarations with lit directives helped. * web: remove admin from elements This commit removes the two references from `elements` to `admin`: the list of UserEvents and a reference to the FlowSearch type, used by the Forms manager to decide how to extract a value. For FlowSearch, a different convention for detecting the type was implemented (instances of the object have a unique fieldname for the value holder). UserEvents and ObjectChangelog have been moved to `components` as they're clearly dependent upon the API. This defers work on removing Admin from Components, as that is (again) references going the wrong way, but that can happen later. * web: remove admin-to-user component reference(s) (#6856) There was only one: AppIcon. This has been moved to `components`. Touching the LibraryApplications page triggered a cyclomatic complexity check. Extracting the expansion block and streamlining the class and style declarations with lit directives helped. * This was supposed to be merged. * web: remove `./element`⇢`./user` references The offender here is UserDevicesList, which despite being in `elements` is only used by the admin/user/UserViewPage. The problem is that UserDevicesList, despite being in `admin`, inherits from `user`, so moving it would have created a new admin⇢user reference, and the whole point of this exercise is to get rid of references that point "up" from the foundational pieces to the views, or that refer to components in sibling applications. After examining UserDevicesList, I realized that *every feature* of MFADevicesList had been overridden: the rows, the columns, the toolbar, and the endpoint all had custom overrides. Nothing was left of MFADevicesList after that. Even the property that the web component used had been completely changed. The only thing they had in common was that they both inherited from `Table`. Refactoring UserDevicesList so that it inherited directly from `Table` and then moving it into `./admin/users` was the obvious and correct step. Both used the same label table, so that went into the `common/labels` folder. Along the way, I cleaned up a few minor details. Just little things, like the repeated invocation of: ``` new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorAdminMETHODDestroy({ id: device.pk }); ``` This is repeated five times, once for each Method. By creating these: ``` const api = new AuthenticatorsApi(DEFAULT_CONFIG); const id = { id: device.pk }; ``` The method invocation could be just `api.authenticatorsMETHODDestroy(id)`, which is easier on the eyes. See the MFADevicesPage for the full example. Similarly, ``` return [ new TableColumn(msg("Name"), ""), new TableColumn(msg("Type"), ""), new TableColumn("") ]; ``` is more straightforward as: ``` const headers = [msg("Name"), msg("Type"), ""]; return headers.map((th) => new TableColumn(th, "")); ``` We've labeled what we're working with, and web developers ought to know that `th` is the HTML code for `table header`. I've had to alter what files are scanned in pre-commit mode; it doesn't handle renamed files very well, and at the moment a file that is renamed is not scanned, as its "new" name is not straightforwardly displayed, not even by `git porcelain`. * web: make the table of column headers look like a table * web: detangle `common` from `elements`. And just like that, `common` no longer has a reference to `elements`. I don't mind this little bit of code duplication if it removes a cycle. What it does point out is that there are bits of `common` that are predicated on the presence of the browser, and that there are bits of `elements` that, if they rely on `common`, can't be disentangled from the application as a whole. Which seems to me that we have two different things going on in common: things about an application, and things about elements that are independent of the application. I'll think about those later. ``` $ rg 'import.*@goauthentik' ./common/ | perl -ne 'm{"(@goauthentik[^"]*)"} && print "$1\n"' | sort | cut -d '/' -f1-2 | uniq | sort @goauthentik/api @goauthentik/common $ ``` * web: odd bug; merge-related? Gonna investigate. * web: build failure thanks to local cache; fixed * web: detangle `components` from `admin`. This was the last inappropriate reference: something from `./components` referencing something in `./admin`, in this case the `ak-event-info` component. Used by both Users and Admin, moving it into `./components` was the obvious correct step. `ak-event-info` is a lookup table relating specific events in the event log to rich, textual representations; in the special case of model changes and email info, even more rich content is available in a dl/dt format. I've tableized the model changes and email info renderer, and I've extracted every event's textual representation into its own method, converting the `switch/case` rendering statement into a `switch/case` dispatch switch. This has the virtue of isolating each unique case and making the dispatch switch short and coherent. The conversion was done mechanistically; I gave the refactorer (Tide, in this case) instructions to duplicate the switch block and then convert every case into a method with a name patterned on the `case`. Going back to the original switch block, it was easy to duplicate the pattern matching and convert it into a dispatch switch. And with this, there are zero cycles in the references between the different "packageable" sections of the UI. The only thing left to do is figure out how to redistribute `./elements` and `./components` in a way that makes sense for each. * Changed function name from 'emailMessageBody' to 'githubIssueMessageBody' to better reflect its usage. * web: added comments about length and purpose of githubIssueMessageBody. * Update web/src/common/labels.ts Co-authored-by: Jens L. Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com> * Unwanted change. --------- Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com> Co-authored-by: Jens L. --- .../admin-overview/cards/RecentEventsCard.ts | 2 +- web/src/admin/events/EventInfo.ts | 442 ---------------- web/src/admin/events/EventListPage.ts | 2 +- web/src/admin/events/EventViewPage.ts | 2 +- web/src/components/ak-event-info.ts | 491 ++++++++++++++++++ web/src/components/events/ObjectChangelog.ts | 2 +- web/src/components/events/UserEvents.ts | 6 +- 7 files changed, 497 insertions(+), 450 deletions(-) delete mode 100644 web/src/admin/events/EventInfo.ts create mode 100644 web/src/components/ak-event-info.ts diff --git a/web/src/admin/admin-overview/cards/RecentEventsCard.ts b/web/src/admin/admin-overview/cards/RecentEventsCard.ts index b02c992b2..11c197dd8 100644 --- a/web/src/admin/admin-overview/cards/RecentEventsCard.ts +++ b/web/src/admin/admin-overview/cards/RecentEventsCard.ts @@ -1,9 +1,9 @@ -import "@goauthentik/admin/events/EventInfo"; import { EventGeo } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { actionToLabel } from "@goauthentik/common/labels"; import { truncate } from "@goauthentik/common/utils"; +import "@goauthentik/components/ak-event-info"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/ModalButton"; diff --git a/web/src/admin/events/EventInfo.ts b/web/src/admin/events/EventInfo.ts deleted file mode 100644 index 0a1b32cc4..000000000 --- a/web/src/admin/events/EventInfo.ts +++ /dev/null @@ -1,442 +0,0 @@ -import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; -import { VERSION } from "@goauthentik/common/constants"; -import { EventContext, EventModel, EventWithContext } from "@goauthentik/common/events"; -import { AKElement } from "@goauthentik/elements/Base"; -import "@goauthentik/elements/Expand"; -import "@goauthentik/elements/Spinner"; -import { PFSize } from "@goauthentik/elements/Spinner"; - -import { msg, str } from "@lit/localize"; -import { CSSResult, TemplateResult, css, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; -import { until } from "lit/directives/until.js"; - -import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import PFCard from "@patternfly/patternfly/components/Card/card.css"; -import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; -import PFList from "@patternfly/patternfly/components/List/list.css"; -import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -import { EventActions, FlowsApi } from "@goauthentik/api"; - -@customElement("ak-event-info") -export class EventInfo extends AKElement { - @property({ attribute: false }) - event!: EventWithContext; - - static get styles(): CSSResult[] { - return [ - PFBase, - PFButton, - PFFlex, - PFCard, - PFList, - PFDescriptionList, - css` - code { - display: block; - white-space: pre-wrap; - word-break: break-all; - } - .pf-l-flex { - justify-content: space-between; - } - .pf-l-flex__item { - min-width: 25%; - } - iframe { - width: 100%; - height: 50rem; - } - `, - ]; - } - - getModelInfo(context: EventModel): TemplateResult { - if (context === null) { - return html`-`; - } - return html`
-
-
-
- ${msg("UID")} -
-
-
${context.pk}
-
-
-
-
- ${msg("Name")} -
-
-
${context.name}
-
-
-
-
- ${msg("App")} -
-
-
${context.app}
-
-
-
-
- ${msg("Model Name")} -
-
-
${context.model_name}
-
-
-
-
`; - } - - getEmailInfo(context: EventContext): TemplateResult { - if (context === null) { - return html`-`; - } - return html`
-
-
- ${msg("Message")} -
-
-
${context.message}
-
-
-
-
- ${msg("Subject")} -
-
-
${context.subject}
-
-
-
-
- ${msg("From")} -
-
-
${context.from_email}
-
-
-
-
- ${msg("To")} -
-
-
- ${(context.to_email as string[]).map((to) => { - return html`
  • ${to}
  • `; - })} -
    -
    -
    -
    `; - } - - defaultResponse(): TemplateResult { - return html`
    -
    -
    ${msg("Context")}
    -
    - ${JSON.stringify(this.event?.context, null, 4)} -
    -
    -
    -
    ${msg("User")}
    -
    - ${JSON.stringify(this.event?.user, null, 4)} -
    -
    -
    `; - } - - buildGitHubIssueUrl(context: EventContext): string { - const httpRequest = this.event.context.http_request as EventContext; - let title = ""; - if (httpRequest) { - title = `${httpRequest?.method} ${httpRequest?.path}`; - } - // https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-issues/about-automation-for-issues-and-pull-requests-with-query-parameters - const fullBody = ` -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Logs** -
    - Stacktrace from authentik - -\`\`\` -${context.message as string} -\`\`\` -
    - - -**Version and Deployment (please complete the following information):** -- authentik version: ${VERSION} -- Deployment: [e.g. docker-compose, helm] - -**Additional context** -Add any other context about the problem here. - `; - return `https://github.com/goauthentik/authentik/issues/ -new?labels=bug,from_authentik&title=${encodeURIComponent(title)} -&body=${encodeURIComponent(fullBody)}`.trim(); - } - - render(): TemplateResult { - if (!this.event) { - return html``; - } - switch (this.event?.action) { - case EventActions.ModelCreated: - case EventActions.ModelUpdated: - case EventActions.ModelDeleted: - return html` -
    ${msg("Affected model:")}
    -
    - ${this.getModelInfo(this.event.context?.model as EventModel)} -
    - `; - case EventActions.AuthorizeApplication: - return html`
    -
    -
    ${msg("Authorized application:")}
    -
    - ${this.getModelInfo( - this.event.context.authorized_application as EventModel, - )} -
    -
    -
    -
    ${msg("Using flow")}
    -
    - ${until( - new FlowsApi(DEFAULT_CONFIG) - .flowsInstancesList({ - flowUuid: this.event.context.flow as string, - }) - .then((resp) => { - return html`${resp.results[0].name}`; - }), - html``, - )} - -
    -
    -
    - ${this.defaultResponse()}`; - case EventActions.EmailSent: - return html`
    ${msg("Email info:")}
    -
    ${this.getEmailInfo(this.event.context)}
    - - - `; - case EventActions.SecretView: - return html`
    ${msg("Secret:")}
    - ${this.getModelInfo(this.event.context.secret as EventModel)}`; - case EventActions.SystemException: - return html`
    -
    -
    ${msg("Exception")}
    - -
    -
    ${this.event.context.message}
    -
    -
    -
    - ${this.defaultResponse()}`; - case EventActions.PropertyMappingException: - return html`
    -
    -
    ${msg("Exception")}
    -
    -
    ${this.event.context.message || this.event.context.error}
    -
    -
    -
    -
    ${msg("Expression")}
    -
    - ${this.event.context.expression} -
    -
    -
    - ${this.defaultResponse()}`; - case EventActions.PolicyException: - return html`
    -
    -
    ${msg("Binding")}
    - ${this.getModelInfo(this.event.context.binding as EventModel)} -
    -
    -
    ${msg("Request")}
    -
    -
      -
    • - ${msg("Object")}: - ${this.getModelInfo( - (this.event.context.request as EventContext) - .obj as EventModel, - )} -
    • -
    • - ${msg("Context")}: - ${JSON.stringify( - (this.event.context.request as EventContext) - .context, - null, - 4, - )} -
    • -
    -
    -
    -
    -
    ${msg("Exception")}
    -
    - ${this.event.context.message || this.event.context.error} -
    -
    -
    - ${this.defaultResponse()}`; - case EventActions.PolicyExecution: - return html`
    -
    -
    ${msg("Binding")}
    - ${this.getModelInfo(this.event.context.binding as EventModel)} -
    -
    -
    ${msg("Request")}
    -
    -
      -
    • - ${msg("Object")}: - ${this.getModelInfo( - (this.event.context.request as EventContext) - .obj as EventModel, - )} -
    • -
    • - ${msg("Context")}: - ${JSON.stringify( - (this.event.context.request as EventContext) - .context, - null, - 4, - )} -
    • -
    -
    -
    -
    -
    ${msg("Result")}
    -
    -
      -
    • - ${msg("Passing")}: - ${(this.event.context.result as EventContext).passing} -
    • -
    • - ${msg("Messages")}: -
        - ${( - (this.event.context.result as EventContext) - .messages as string[] - ).map((msg) => { - return html`
      • ${msg}
      • `; - })} -
      -
    • -
    -
    -
    -
    - ${this.defaultResponse()}`; - case EventActions.ConfigurationError: - return html`
    ${this.event.context.message}
    - ${this.defaultResponse()}`; - case EventActions.UpdateAvailable: - return html`
    ${msg("New version available!")}
    - - ${this.event.context.new_version} - `; - // Action types which typically don't record any extra context. - // If context is not empty, we fall to the default response. - case EventActions.Login: - if ("using_source" in this.event.context) { - return html`
    -
    -
    ${msg("Using source")}
    - ${this.getModelInfo(this.event.context.using_source as EventModel)} -
    -
    `; - } - return this.defaultResponse(); - case EventActions.LoginFailed: - return html`
    - ${msg(str`Attempted to log in as ${this.event.context.username}`)} -
    - ${this.defaultResponse()}`; - case EventActions.Logout: - if (Object.keys(this.event.context).length === 0) { - return html`${msg("No additional data available.")}`; - } - return this.defaultResponse(); - case EventActions.SystemTaskException: - return html`
    -
    -
    ${msg("Exception")}
    -
    -
    ${this.event.context.message}
    -
    -
    -
    `; - default: - return this.defaultResponse(); - } - } -} diff --git a/web/src/admin/events/EventListPage.ts b/web/src/admin/events/EventListPage.ts index 38b310106..4d42e8c74 100644 --- a/web/src/admin/events/EventListPage.ts +++ b/web/src/admin/events/EventListPage.ts @@ -1,9 +1,9 @@ -import "@goauthentik/admin/events/EventInfo"; import { EventGeo } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { actionToLabel } from "@goauthentik/common/labels"; import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/components/ak-event-info"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; diff --git a/web/src/admin/events/EventViewPage.ts b/web/src/admin/events/EventViewPage.ts index ad23514eb..b5351840a 100644 --- a/web/src/admin/events/EventViewPage.ts +++ b/web/src/admin/events/EventViewPage.ts @@ -1,8 +1,8 @@ -import "@goauthentik/admin/events/EventInfo"; import { EventGeo } from "@goauthentik/admin/events/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { actionToLabel } from "@goauthentik/common/labels"; +import "@goauthentik/components/ak-event-info"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/PageHeader"; diff --git a/web/src/components/ak-event-info.ts b/web/src/components/ak-event-info.ts new file mode 100644 index 000000000..6901f31a8 --- /dev/null +++ b/web/src/components/ak-event-info.ts @@ -0,0 +1,491 @@ +import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; +import { VERSION } from "@goauthentik/common/constants"; +import { EventContext, EventModel, EventWithContext } from "@goauthentik/common/events"; +import { AKElement } from "@goauthentik/elements/Base"; +import "@goauthentik/elements/Expand"; +import "@goauthentik/elements/Spinner"; +import { PFSize } from "@goauthentik/elements/Spinner"; + +import { msg, str } from "@lit/localize"; +import { CSSResult, TemplateResult, css, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { map } from "lit/directives/map.js"; +import { until } from "lit/directives/until.js"; + +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; +import PFList from "@patternfly/patternfly/components/List/list.css"; +import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; + +import { EventActions, FlowsApi } from "@goauthentik/api"; + +type Pair = [string, string | number | EventContext | EventModel | string[] | TemplateResult]; + +// https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-issues/about-automation-for-issues-and-pull-requests-with-query-parameters + +// This is the template message body with our stacktrace passed to github via a querystring. It is +// 702 bytes long in UTF-8. [As of July +// 2023](https://saturncloud.io/blog/what-is-the-maximum-length-of-a-url-in-different-browsers/), +// the longest URL (not query string, **URL**) passable via this method is 2048 bytes. This is a bit +// of a hack, but it will get the top of the context across even if it exceeds the limit of the more +// restrictive browsers. + +const githubIssueMessageBody = (context: EventContext) => ` +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Logs** +
    + Stacktrace from authentik + +\`\`\` +${context.message as string} +\`\`\` +
    + + +**Version and Deployment (please complete the following information):** +- authentik version: ${VERSION} +- Deployment: [e.g. docker-compose, helm] + +**Additional context** +Add any other context about the problem here. + `; + +@customElement("ak-event-info") +export class EventInfo extends AKElement { + @property({ attribute: false }) + event!: EventWithContext; + + static get styles(): CSSResult[] { + return [ + PFBase, + PFButton, + PFFlex, + PFCard, + PFList, + PFDescriptionList, + css` + code { + display: block; + white-space: pre-wrap; + word-break: break-all; + } + .pf-l-flex { + justify-content: space-between; + } + .pf-l-flex__item { + min-width: 25%; + } + iframe { + width: 100%; + height: 50rem; + } + `, + ]; + } + + renderDescriptionGroup([term, description]: Pair) { + return html`
    +
    + ${term} +
    +
    +
    ${description}
    +
    +
    `; + } + + getModelInfo(context: EventModel): TemplateResult { + if (context === null) { + return html`-`; + } + + const modelFields: Pair[] = [ + [msg("UID"), context.pk], + [msg("Name"), context.name], + [msg("App"), context.app], + [msg("Model Name"), context.model_name], + ]; + + return html`
    +
    + ${map(modelFields, this.renderDescriptionGroup)} +
    +
    `; + } + + getEmailInfo(context: EventContext): TemplateResult { + if (context === null) { + return html`-`; + } + + // prettier-ignore + const emailFields: Pair[] = [ + [msg("Message"), context.message], + [msg("Subject"), context.subject], + [msg("From"), context.from_email], + [msg("To"), html`${(context.to_email as string[]).map((to) => { + return html`
  • ${to}
  • `; + })}`], + ]; + + return html`
    + ${map(emailFields, this.renderDescriptionGroup)} +
    `; + } + + renderDefaultResponse(): TemplateResult { + return html`
    +
    +
    ${msg("Context")}
    +
    + ${JSON.stringify(this.event?.context, null, 4)} +
    +
    +
    +
    ${msg("User")}
    +
    + ${JSON.stringify(this.event?.user, null, 4)} +
    +
    +
    `; + } + + buildGitHubIssueUrl(context: EventContext): string { + const httpRequest = this.event.context.http_request as EventContext; + const title = httpRequest ? `${httpRequest?.method} ${httpRequest?.path}` : ""; + + return [ + "https://github.com/goauthentik/authentik/issues/new", + "?labels=bug,from_authentik", + `&title=${encodeURIComponent(title)}`, + `&body=${encodeURIComponent(githubIssueMessageBody(context))}`, + ] + .join("") + .trim(); + } + + // It's commonplace not to put the return type on most functions in Typescript. In this case, + // however, putting this return type creates a virtuous check of *all* the subrenderers to + // ensure that all of them return what we're expecting. + + render(): TemplateResult { + if (!this.event) { + return html``; + } + + switch (this.event?.action) { + case EventActions.ModelCreated: + case EventActions.ModelUpdated: + case EventActions.ModelDeleted: + return this.renderModelChanged(); + + case EventActions.AuthorizeApplication: + return this.renderAuthorizeApplication(); + + case EventActions.EmailSent: + return this.renderEmailSent(); + + case EventActions.SecretView: + return this.renderSecretView(); + + case EventActions.SystemException: + return this.renderSystemException(); + + case EventActions.PropertyMappingException: + return this.renderPropertyMappingException(); + + case EventActions.PolicyException: + return this.renderPolicyException(); + + case EventActions.PolicyExecution: + return this.renderPolicyExecution(); + + case EventActions.ConfigurationError: + return this.renderConfigurationError(); + + case EventActions.UpdateAvailable: + return this.renderUpdateAvailable(); + + // Action types which typically don't record any extra context. + // If context is not empty, we fall to the default response. + case EventActions.Login: + return this.renderLogin(); + + case EventActions.LoginFailed: + return this.renderLoginFailed(); + + case EventActions.Logout: + return this.renderLogout(); + + case EventActions.SystemTaskException: + return this.renderSystemTaskException(); + + default: + return this.renderDefaultResponse(); + } + } + + renderModelChanged() { + return html` +
    ${msg("Affected model:")}
    +
    + ${this.getModelInfo(this.event.context?.model as EventModel)} +
    + `; + } + + renderAuthorizeApplication() { + return html`
    +
    +
    ${msg("Authorized application:")}
    +
    + ${this.getModelInfo( + this.event.context.authorized_application as EventModel, + )} +
    +
    +
    +
    ${msg("Using flow")}
    +
    + ${until( + new FlowsApi(DEFAULT_CONFIG) + .flowsInstancesList({ + flowUuid: this.event.context.flow as string, + }) + .then((resp) => { + return html`${resp.results[0].name}`; + }), + html``, + )} + +
    +
    +
    + ${this.renderDefaultResponse()}`; + } + + renderEmailSent() { + return html`
    ${msg("Email info:")}
    +
    ${this.getEmailInfo(this.event.context)}
    + + + `; + } + + renderSecretView() { + return html`
    ${msg("Secret:")}
    + ${this.getModelInfo(this.event.context.secret as EventModel)}`; + } + + renderSystemException() { + return html`
    +
    +
    ${msg("Exception")}
    + +
    +
    ${this.event.context.message}
    +
    +
    +
    + ${this.renderDefaultResponse()}`; + } + + renderPropertyMappingException() { + return html`
    +
    +
    ${msg("Exception")}
    +
    +
    ${this.event.context.message || this.event.context.error}
    +
    +
    +
    +
    ${msg("Expression")}
    +
    + ${this.event.context.expression} +
    +
    +
    + ${this.renderDefaultResponse()}`; + } + + renderPolicyException() { + return html`
    +
    +
    ${msg("Binding")}
    + ${this.getModelInfo(this.event.context.binding as EventModel)} +
    +
    +
    ${msg("Request")}
    +
    +
      +
    • + ${msg("Object")}: + ${this.getModelInfo( + (this.event.context.request as EventContext).obj as EventModel, + )} +
    • +
    • + ${msg("Context")}: + ${JSON.stringify( + (this.event.context.request as EventContext).context, + null, + 4, + )} +
    • +
    +
    +
    +
    +
    ${msg("Exception")}
    +
    + ${this.event.context.message || this.event.context.error} +
    +
    +
    + ${this.renderDefaultResponse()}`; + } + + renderPolicyExecution() { + return html`
    +
    +
    ${msg("Binding")}
    + ${this.getModelInfo(this.event.context.binding as EventModel)} +
    +
    +
    ${msg("Request")}
    +
    +
      +
    • + ${msg("Object")}: + ${this.getModelInfo( + (this.event.context.request as EventContext).obj as EventModel, + )} +
    • +
    • + ${msg("Context")}: + ${JSON.stringify( + (this.event.context.request as EventContext).context, + null, + 4, + )} +
    • +
    +
    +
    +
    +
    ${msg("Result")}
    +
    +
      +
    • + ${msg("Passing")}: + ${(this.event.context.result as EventContext).passing} +
    • +
    • + ${msg("Messages")}: +
        + ${( + (this.event.context.result as EventContext) + .messages as string[] + ).map((msg) => { + return html`
      • ${msg}
      • `; + })} +
      +
    • +
    +
    +
    +
    + ${this.renderDefaultResponse()}`; + } + + renderConfigurationError() { + return html`
    ${this.event.context.message}
    + ${this.renderDefaultResponse()}`; + } + + renderUpdateAvailable() { + return html`
    ${msg("New version available")}
    + + ${this.event.context.new_version} + `; + // Action types which typically don't record any extra context. + // If context is not empty, we fall to the default response. + } + + renderLogin() { + if ("using_source" in this.event.context) { + return html`
    +
    +
    ${msg("Using source")}
    + ${this.getModelInfo(this.event.context.using_source as EventModel)} +
    +
    `; + } + return this.renderDefaultResponse(); + } + + renderLoginFailed() { + return html`
    + ${msg(str`Attempted to log in as ${this.event.context.username}`)} +
    + ${this.renderDefaultResponse()}`; + } + + renderLogout() { + if (Object.keys(this.event.context).length === 0) { + return html`${msg("No additional data available.")}`; + } + return this.renderDefaultResponse(); + } + + renderSystemTaskException() { + return html`
    +
    +
    ${msg("Exception")}
    +
    +
    ${this.event.context.message}
    +
    +
    +
    `; + } +} diff --git a/web/src/components/events/ObjectChangelog.ts b/web/src/components/events/ObjectChangelog.ts index d9e53000f..160a98d73 100644 --- a/web/src/components/events/ObjectChangelog.ts +++ b/web/src/components/events/ObjectChangelog.ts @@ -1,7 +1,7 @@ -import "@goauthentik/admin/events/EventInfo"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/components/ak-event-info"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/ModalButton"; diff --git a/web/src/components/events/UserEvents.ts b/web/src/components/events/UserEvents.ts index 6820953d2..8b8065792 100644 --- a/web/src/components/events/UserEvents.ts +++ b/web/src/components/events/UserEvents.ts @@ -1,15 +1,13 @@ -import "@goauthentik/admin/events/EventInfo"; -import "@goauthentik/admin/events/EventInfo"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EventWithContext } from "@goauthentik/common/events"; import { actionToLabel } from "@goauthentik/common/labels"; import { uiConfig } from "@goauthentik/common/ui/config"; +import "@goauthentik/components/ak-event-info"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/SpinnerButton"; -import { PaginatedResponse } from "@goauthentik/elements/table/Table"; -import { Table, TableColumn } from "@goauthentik/elements/table/Table"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/elements/table/Table"; import { msg, str } from "@lit/localize"; import { TemplateResult, html } from "lit";