Compare commits
	
		
			6 Commits
		
	
	
		
			tests/e2e/
			...
			policies/p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b3883f7fbf | |||
| 87c6b0128a | |||
| b243c97916 | |||
| 3f66527521 | |||
| 2f7c258657 | |||
| 917c90374f | 
| @ -1,11 +1,26 @@ | ||||
| """Expression Policy API""" | ||||
|  | ||||
| from drf_spectacular.utils import OpenApiResponse, extend_schema | ||||
| from guardian.shortcuts import get_objects_for_user | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import CharField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.events.logs import LogEventSerializer, capture_logs | ||||
| from authentik.policies.api.exec import PolicyTestResultSerializer, PolicyTestSerializer | ||||
| from authentik.policies.api.policies import PolicySerializer | ||||
| from authentik.policies.expression.evaluator import PolicyEvaluator | ||||
| from authentik.policies.expression.models import ExpressionPolicy | ||||
| from authentik.policies.models import PolicyBinding | ||||
| from authentik.policies.process import PolicyProcess | ||||
| from authentik.policies.types import PolicyRequest | ||||
| from authentik.rbac.decorators import permission_required | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class ExpressionPolicySerializer(PolicySerializer): | ||||
| @ -30,3 +45,50 @@ class ExpressionPolicyViewSet(UsedByMixin, ModelViewSet): | ||||
|     filterset_fields = "__all__" | ||||
|     ordering = ["name"] | ||||
|     search_fields = ["name"] | ||||
|  | ||||
|     class ExpressionPolicyTestSerializer(PolicyTestSerializer): | ||||
|         """Expression policy test serializer""" | ||||
|  | ||||
|         expression = CharField() | ||||
|  | ||||
|     @permission_required("authentik_policies.view_policy") | ||||
|     @extend_schema( | ||||
|         request=ExpressionPolicyTestSerializer(), | ||||
|         responses={ | ||||
|             200: PolicyTestResultSerializer(), | ||||
|             400: OpenApiResponse(description="Invalid parameters"), | ||||
|         }, | ||||
|     ) | ||||
|     @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) | ||||
|     def test(self, request: Request, pk: str) -> Response: | ||||
|         """Test policy""" | ||||
|         policy = self.get_object() | ||||
|         test_params = self.ExpressionPolicyTestSerializer(data=request.data) | ||||
|         if not test_params.is_valid(): | ||||
|             return Response(test_params.errors, status=400) | ||||
|  | ||||
|         # User permission check, only allow policy testing for users that are readable | ||||
|         users = get_objects_for_user(request.user, "authentik_core.view_user").filter( | ||||
|             pk=test_params.validated_data["user"].pk | ||||
|         ) | ||||
|         if not users.exists(): | ||||
|             return Response(status=400) | ||||
|  | ||||
|         policy.expression = test_params.validated_data["expression"] | ||||
|  | ||||
|         p_request = PolicyRequest(users.first()) | ||||
|         p_request.debug = True | ||||
|         p_request.set_http_request(self.request) | ||||
|         p_request.context = test_params.validated_data.get("context", {}) | ||||
|  | ||||
|         proc = PolicyProcess(PolicyBinding(policy=policy), p_request, None) | ||||
|         with capture_logs() as logs: | ||||
|             result = proc.execute() | ||||
|         log_messages = [] | ||||
|         for log in logs: | ||||
|             if log.attributes.get("process", "") == "PolicyProcess": | ||||
|                 continue | ||||
|             log_messages.append(LogEventSerializer(log).data) | ||||
|         result.log_messages = log_messages | ||||
|         response = PolicyTestResultSerializer(result) | ||||
|         return Response(response.data) | ||||
|  | ||||
							
								
								
									
										52
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								schema.yml
									
									
									
									
									
								
							| @ -12813,6 +12813,43 @@ paths: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /policies/expression/{policy_uuid}/test/: | ||||
|     post: | ||||
|       operationId: policies_expression_test_create | ||||
|       description: Test policy | ||||
|       parameters: | ||||
|       - in: path | ||||
|         name: policy_uuid | ||||
|         schema: | ||||
|           type: string | ||||
|           format: uuid | ||||
|         description: A UUID string identifying this Expression Policy. | ||||
|         required: true | ||||
|       tags: | ||||
|       - policies | ||||
|       requestBody: | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/ExpressionPolicyTestRequest' | ||||
|         required: true | ||||
|       security: | ||||
|       - authentik: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/PolicyTestResult' | ||||
|           description: '' | ||||
|         '400': | ||||
|           description: Invalid parameters | ||||
|         '403': | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/GenericError' | ||||
|           description: '' | ||||
|   /policies/expression/{policy_uuid}/used_by/: | ||||
|     get: | ||||
|       operationId: policies_expression_used_by_list | ||||
| @ -42280,6 +42317,21 @@ components: | ||||
|       required: | ||||
|       - expression | ||||
|       - name | ||||
|     ExpressionPolicyTestRequest: | ||||
|       type: object | ||||
|       description: Expression policy test serializer | ||||
|       properties: | ||||
|         user: | ||||
|           type: integer | ||||
|         context: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|         expression: | ||||
|           type: string | ||||
|           minLength: 1 | ||||
|       required: | ||||
|       - expression | ||||
|       - user | ||||
|     ExtraRoleObjectPermission: | ||||
|       type: object | ||||
|       description: User permission with additional object-related data | ||||
|  | ||||
| @ -8,6 +8,7 @@ import "@goauthentik/admin/policies/password/PasswordPolicyForm"; | ||||
| import "@goauthentik/admin/policies/reputation/ReputationPolicyForm"; | ||||
| import "@goauthentik/admin/rbac/ObjectPermissionModal"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { PFSize } from "@goauthentik/common/enums"; | ||||
| import { PFColor } from "@goauthentik/elements/Label"; | ||||
| import "@goauthentik/elements/forms/ConfirmationForm"; | ||||
| import "@goauthentik/elements/forms/DeleteBulkForm"; | ||||
| @ -21,7 +22,6 @@ import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; | ||||
| import { msg, str } from "@lit/localize"; | ||||
| import { TemplateResult, html } from "lit"; | ||||
| import { customElement, property } from "lit/decorators.js"; | ||||
| import { ifDefined } from "lit/directives/if-defined.js"; | ||||
|  | ||||
| import { PoliciesApi, Policy } from "@goauthentik/api"; | ||||
|  | ||||
| @ -71,7 +71,12 @@ export class PolicyListPage extends TablePage<Policy> { | ||||
|                           ${msg("Warning: Policy is not assigned.")} | ||||
|                       </ak-label>`}`, | ||||
|             html`${item.verboseName}`, | ||||
|             html` <ak-forms-modal> | ||||
|             html` <ak-forms-modal | ||||
|                     size=${item.component === "ak-policy-expression-form" | ||||
|                         ? PFSize.XLarge | ||||
|                         : PFSize.Large} | ||||
|                     ?fullHeight=${item.component === "ak-policy-expression-form"} | ||||
|                 > | ||||
|                     <span slot="submit"> ${msg("Update")} </span> | ||||
|                     <span slot="header"> ${msg(str`Update ${item.verboseName}`)} </span> | ||||
|                     <ak-proxy-form | ||||
| @ -79,7 +84,7 @@ export class PolicyListPage extends TablePage<Policy> { | ||||
|                         .args=${{ | ||||
|                             instancePk: item.pk, | ||||
|                         }} | ||||
|                         type=${ifDefined(item.component)} | ||||
|                         type=${item.component} | ||||
|                     > | ||||
|                     </ak-proxy-form> | ||||
|                     <button slot="trigger" class="pf-c-button pf-m-plain"> | ||||
|  | ||||
| @ -87,7 +87,10 @@ export class PolicyWizard extends AKElement { | ||||
|                             slot=${`type-${type.component}-${type.modelName}`} | ||||
|                             .sidebarLabel=${() => msg(str`Create ${type.name}`)} | ||||
|                         > | ||||
|                             <ak-proxy-form type=${type.component}></ak-proxy-form> | ||||
|                             <ak-proxy-form | ||||
|                                 ?showPreview=${false} | ||||
|                                 type=${type.component} | ||||
|                             ></ak-proxy-form> | ||||
|                         </ak-wizard-page-form> | ||||
|                     `; | ||||
|                 })} | ||||
|  | ||||
| @ -1,25 +1,64 @@ | ||||
| import { BasePolicyForm } from "@goauthentik/admin/policies/BasePolicyForm"; | ||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||
| import { docLink } from "@goauthentik/common/global"; | ||||
| import { me } from "@goauthentik/common/users"; | ||||
| import { first } from "@goauthentik/common/utils"; | ||||
| import "@goauthentik/elements/CodeMirror"; | ||||
| import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror"; | ||||
| import "@goauthentik/elements/EmptyState"; | ||||
| import "@goauthentik/elements/forms/FormGroup"; | ||||
| import "@goauthentik/elements/forms/HorizontalFormElement"; | ||||
| import YAML from "yaml"; | ||||
|  | ||||
| import { msg } from "@lit/localize"; | ||||
| import { TemplateResult, html } from "lit"; | ||||
| import { customElement } from "lit/decorators.js"; | ||||
| import { CSSResult, TemplateResult, html, nothing } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators.js"; | ||||
| import { ifDefined } from "lit/directives/if-defined.js"; | ||||
|  | ||||
| import { ExpressionPolicy, PoliciesApi } from "@goauthentik/api"; | ||||
| import PFTitle from "@patternfly/patternfly/components/Title/title.css"; | ||||
| import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; | ||||
| import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; | ||||
|  | ||||
| import { | ||||
|     CoreApi, | ||||
|     CoreUsersListRequest, | ||||
|     ExpressionPolicy, | ||||
|     PoliciesApi, | ||||
|     PolicyTestResult, | ||||
|     ResponseError, | ||||
|     SessionUser, | ||||
|     User, | ||||
|     ValidationErrorFromJSON, | ||||
| } from "@goauthentik/api"; | ||||
|  | ||||
| @customElement("ak-policy-expression-form") | ||||
| export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> { | ||||
|     loadInstance(pk: string): Promise<ExpressionPolicy> { | ||||
|         return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionRetrieve({ | ||||
|     @property({ type: Boolean }) | ||||
|     showPreview = true; | ||||
|  | ||||
|     @state() | ||||
|     preview?: PolicyTestResult; | ||||
|  | ||||
|     @state() | ||||
|     previewError?: string[]; | ||||
|  | ||||
|     @state() | ||||
|     user?: SessionUser; | ||||
|  | ||||
|     @state() | ||||
|     previewLoading = false; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return super.styles.concat(PFGrid, PFStack, PFTitle); | ||||
|     } | ||||
|  | ||||
|     async loadInstance(pk: string): Promise<ExpressionPolicy> { | ||||
|         const policy = await new PoliciesApi(DEFAULT_CONFIG).policiesExpressionRetrieve({ | ||||
|             policyUuid: pk, | ||||
|         }); | ||||
|         this.user = await me(); | ||||
|         await this.refreshPreview(policy); | ||||
|         return policy; | ||||
|     } | ||||
|  | ||||
|     async send(data: ExpressionPolicy): Promise<ExpressionPolicy> { | ||||
| @ -35,10 +74,196 @@ export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     _shouldRefresh = false; | ||||
|     _timer = 0; | ||||
|  | ||||
|     connectedCallback(): void { | ||||
|         super.connectedCallback(); | ||||
|         if (!this.showPreview) { | ||||
|             return; | ||||
|         } | ||||
|         // Only check if we should update once a second, to prevent spamming API requests | ||||
|         // when many fields are edited | ||||
|         const minUpdateDelay = 1000; | ||||
|         this._timer = setInterval(() => { | ||||
|             if (this._shouldRefresh) { | ||||
|                 this.refreshPreview(); | ||||
|                 this._shouldRefresh = false; | ||||
|             } | ||||
|         }, minUpdateDelay) as unknown as number; | ||||
|     } | ||||
|  | ||||
|     disconnectedCallback(): void { | ||||
|         super.disconnectedCallback(); | ||||
|         if (!this.showPreview) { | ||||
|             return; | ||||
|         } | ||||
|         clearTimeout(this._timer); | ||||
|     } | ||||
|  | ||||
|     async refreshPreview(policy?: ExpressionPolicy): Promise<void> { | ||||
|         if (!policy) { | ||||
|             policy = this.serializeForm(); | ||||
|             if (!policy) { | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         this.previewLoading = true; | ||||
|         try { | ||||
|             interface testpolicy { | ||||
|                 expression: string; | ||||
|                 user?: number; | ||||
|                 context?: { [key: string]: unknown }; | ||||
|             } | ||||
|             const tp = policy as unknown as testpolicy; | ||||
|             this.preview = await new PoliciesApi(DEFAULT_CONFIG).policiesExpressionTestCreate({ | ||||
|                 expressionPolicyTestRequest: { | ||||
|                     expression: tp.expression, | ||||
|                     user: tp.user || this.user?.user.pk || 0, | ||||
|                     context: tp.context || {}, | ||||
|                 }, | ||||
|                 policyUuid: this.instancePk || "", | ||||
|             }); | ||||
|             this.previewError = undefined; | ||||
|         } catch (exc) { | ||||
|             const errorMessage = ValidationErrorFromJSON( | ||||
|                 await (exc as ResponseError).response.json(), | ||||
|             ); | ||||
|             this.previewError = errorMessage.nonFieldErrors; | ||||
|         } finally { | ||||
|             this.previewLoading = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     renderForm(): TemplateResult { | ||||
|         return html`<div class="pf-l-grid pf-m-gutter"> | ||||
|             <div class="pf-l-grid__item pf-m-6-col pf-l-stack"> | ||||
|                 <div class="pf-c-form pf-m-horizontal pf-l-stack__item"> | ||||
|                     ${this.renderEditForm()} | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="pf-l-grid__item pf-m-6-col">${this.renderPreview()}</div> | ||||
|         </div> `; | ||||
|     } | ||||
|  | ||||
|     renderPreview(): TemplateResult { | ||||
|         return html` | ||||
|             <div class="pf-l-grid pf-m-gutter"> | ||||
|                 <div class="pf-c-card pf-l-grid__item pf-m-12-col"> | ||||
|                     <div class="pf-c-card__title">${msg("Test parameters")}</div> | ||||
|                     <div class="pf-c-card__body pf-c-form pf-m-horizontal"> | ||||
|                         <ak-form-element-horizontal label=${msg("User")} name="user"> | ||||
|                             <ak-search-select | ||||
|                                 .fetchObjects=${async (query?: string): Promise<User[]> => { | ||||
|                                     const args: CoreUsersListRequest = { | ||||
|                                         ordering: "username", | ||||
|                                     }; | ||||
|                                     if (query !== undefined) { | ||||
|                                         args.search = query; | ||||
|                                     } | ||||
|                                     const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList( | ||||
|                                         args, | ||||
|                                     ); | ||||
|                                     return users.results; | ||||
|                                 }} | ||||
|                                 .renderElement=${(user: User): string => { | ||||
|                                     return user.username; | ||||
|                                 }} | ||||
|                                 .renderDescription=${(user: User): TemplateResult => { | ||||
|                                     return html`${user.name}`; | ||||
|                                 }} | ||||
|                                 .value=${(user: User | undefined): number | undefined => { | ||||
|                                     return user?.pk; | ||||
|                                 }} | ||||
|                                 .selected=${(user: User): boolean => { | ||||
|                                     return this.user?.user.pk === user.pk; | ||||
|                                 }} | ||||
|                             > | ||||
|                             </ak-search-select> | ||||
|                         </ak-form-element-horizontal> | ||||
|                         <ak-form-element-horizontal label=${msg("Context")} name="context"> | ||||
|                             <ak-codemirror mode=${CodeMirrorMode.YAML} value=${YAML.stringify({})}> | ||||
|                             </ak-codemirror> | ||||
|                         </ak-form-element-horizontal> | ||||
|                     </div> | ||||
|                     <div class="pf-c-card__footer"> | ||||
|                         <button | ||||
|                             class="pf-c-button pf-m-primary" | ||||
|                             @click=${() => { | ||||
|                                 this.refreshPreview(); | ||||
|                             }} | ||||
|                         > | ||||
|                             ${msg("Execute")} | ||||
|                         </button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="pf-c-card pf-l-grid__item pf-m-12-col"> | ||||
|                     <div class="pf-c-card__title">${msg("Test results")}</div> | ||||
|                     ${this.previewLoading | ||||
|                         ? html`<ak-empty-state loading></ak-empty-state>` | ||||
|                         : html`<div class="pf-c-card__body pf-c-form pf-m-horizontal"> | ||||
|                               <ak-form-element-horizontal label=${msg("Passing")}> | ||||
|                                   <div class="pf-c-form__group-label"> | ||||
|                                       <div class="c-form__horizontal-group"> | ||||
|                                           <span class="pf-c-form__label-text"> | ||||
|                                               <ak-status-label | ||||
|                                                   ?good=${this.preview?.passing} | ||||
|                                               ></ak-status-label> | ||||
|                                           </span> | ||||
|                                       </div> | ||||
|                                   </div> | ||||
|                               </ak-form-element-horizontal> | ||||
|                               <ak-form-element-horizontal label=${msg("Messages")}> | ||||
|                                   <div class="pf-c-form__group-label"> | ||||
|                                       <div class="c-form__horizontal-group"> | ||||
|                                           <ul> | ||||
|                                               ${(this.preview?.messages || []).length > 0 | ||||
|                                                   ? this.preview?.messages?.map((m) => { | ||||
|                                                         return html`<li> | ||||
|                                                             <span class="pf-c-form__label-text" | ||||
|                                                                 >${m}</span | ||||
|                                                             > | ||||
|                                                         </li>`; | ||||
|                                                     }) | ||||
|                                                   : html`<li> | ||||
|                                                         <span class="pf-c-form__label-text">-</span> | ||||
|                                                     </li>`} | ||||
|                                           </ul> | ||||
|                                       </div> | ||||
|                                   </div> | ||||
|                               </ak-form-element-horizontal> | ||||
|  | ||||
|                               <ak-form-element-horizontal label=${msg("Log messages")}> | ||||
|                                   <div class="pf-c-form__group-label"> | ||||
|                                       <div class="c-form__horizontal-group"> | ||||
|                                           <dl class="pf-c-description-list pf-m-horizontal"> | ||||
|                                               <ak-log-viewer | ||||
|                                                   .logs=${this.preview?.logMessages} | ||||
|                                               ></ak-log-viewer> | ||||
|                                           </dl> | ||||
|                                       </div> | ||||
|                                   </div> | ||||
|                               </ak-form-element-horizontal> | ||||
|                           </div>`} | ||||
|                 </div> | ||||
|                 ${this.previewError | ||||
|                     ? html` | ||||
|                           <div class="pf-c-card pf-l-grid__item pf-m-12-col"> | ||||
|                               <div class="pf-c-card__body">${msg("Preview errors")}</div> | ||||
|                               <div class="pf-c-card__body"> | ||||
|                                   ${this.previewError.map((err) => html`<pre>${err}</pre>`)} | ||||
|                               </div> | ||||
|                           </div> | ||||
|                       ` | ||||
|                     : nothing} | ||||
|             </div> | ||||
|         `; | ||||
|     } | ||||
|  | ||||
|     renderEditForm(): TemplateResult { | ||||
|         return html` <span> | ||||
|                 ${msg( | ||||
|                     "Executes the python snippet to determine whether to allow or deny a request.", | ||||
|                     "Executes the Python snippet to determine whether to allow or deny a request.", | ||||
|                 )} | ||||
|             </span> | ||||
|             <ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name"> | ||||
| @ -80,6 +305,9 @@ export class ExpressionPolicyForm extends BasePolicyForm<ExpressionPolicy> { | ||||
|                         <ak-codemirror | ||||
|                             mode=${CodeMirrorMode.Python} | ||||
|                             value="${ifDefined(this.instance?.expression)}" | ||||
|                             @change=${() => { | ||||
|                                 this._shouldRefresh = true; | ||||
|                             }} | ||||
|                         > | ||||
|                         </ak-codemirror> | ||||
|                         <p class="pf-c-form__helper-text"> | ||||
|  | ||||
| @ -20,8 +20,11 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) { | ||||
|     } | ||||
|  | ||||
|     renderStatusBanner() { | ||||
|         if (!this.licenseSummary) { | ||||
|             return nothing; | ||||
|         } | ||||
|         // Check if we're in the correct interface to render a banner | ||||
|         switch (this.licenseSummary.status) { | ||||
|         switch (this.licenseSummary?.status) { | ||||
|             // user warning is both on admin interface and user interface | ||||
|             case LicenseSummaryStatusEnum.LimitExceededUser: | ||||
|                 if ( | ||||
| @ -46,7 +49,7 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) { | ||||
|                 break; | ||||
|         } | ||||
|         let message = ""; | ||||
|         switch (this.licenseSummary.status) { | ||||
|         switch (this.licenseSummary?.status) { | ||||
|             case LicenseSummaryStatusEnum.LimitExceededAdmin: | ||||
|             case LicenseSummaryStatusEnum.LimitExceededUser: | ||||
|                 message = msg( | ||||
| @ -83,13 +86,16 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) { | ||||
|     } | ||||
|  | ||||
|     renderFlagBanner() { | ||||
|         if (!this.licenseSummary) { | ||||
|             return nothing; | ||||
|         } | ||||
|         return html` | ||||
|             ${this.licenseSummary.licenseFlags.includes(LicenseFlagsEnum.Trial) | ||||
|             ${this.licenseSummary?.licenseFlags.includes(LicenseFlagsEnum.Trial) | ||||
|                 ? html`<div class="pf-c-banner pf-m-sticky pf-m-gold"> | ||||
|                       ${msg("This authentik instance uses a Trial license.")} | ||||
|                   </div>` | ||||
|                 : nothing} | ||||
|             ${this.licenseSummary.licenseFlags.includes(LicenseFlagsEnum.NonProduction) | ||||
|             ${this.licenseSummary?.licenseFlags.includes(LicenseFlagsEnum.NonProduction) | ||||
|                 ? html`<div class="pf-c-banner pf-m-sticky pf-m-gold"> | ||||
|                       ${msg("This authentik instance uses a Non-production license.")} | ||||
|                   </div>` | ||||
|  | ||||
| @ -40,6 +40,9 @@ export class ModalButton extends AKElement { | ||||
|     @property() | ||||
|     size: PFSize = PFSize.Large; | ||||
|  | ||||
|     @property({ type: Boolean }) | ||||
|     fullHeight = false; | ||||
|  | ||||
|     @property({ type: Boolean }) | ||||
|     open = false; | ||||
|  | ||||
| @ -69,6 +72,9 @@ export class ModalButton extends AKElement { | ||||
|                 .pf-c-modal-box.pf-m-xl { | ||||
|                     --pf-c-modal-box--Width: calc(1.5 * var(--pf-c-modal-box--m-lg--lg--MaxWidth)); | ||||
|                 } | ||||
|                 :host([fullHeight]) .pf-c-modal-box { | ||||
|                     height: 100%; | ||||
|                 } | ||||
|             `, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
| @ -14,7 +14,20 @@ import { LogEvent, LogLevelEnum } from "@goauthentik/api"; | ||||
| @customElement("ak-log-viewer") | ||||
| export class LogViewer extends Table<LogEvent> { | ||||
|     @property({ attribute: false }) | ||||
|     logs?: LogEvent[] = []; | ||||
|     set logs(val: LogEvent[]) { | ||||
|         this.data = { | ||||
|             pagination: { | ||||
|                 next: 0, | ||||
|                 previous: 0, | ||||
|                 count: val.length || 0, | ||||
|                 current: 1, | ||||
|                 totalPages: 1, | ||||
|                 startIndex: 1, | ||||
|                 endIndex: val.length || 0, | ||||
|             }, | ||||
|             results: val, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     expandable = true; | ||||
|     paginated = false; | ||||
| @ -24,7 +37,8 @@ export class LogViewer extends Table<LogEvent> { | ||||
|     } | ||||
|  | ||||
|     async apiEndpoint(): Promise<PaginatedResponse<LogEvent>> { | ||||
|         return { | ||||
|         return ( | ||||
|             this.data || { | ||||
|                 pagination: { | ||||
|                     next: 0, | ||||
|                     previous: 0, | ||||
| @ -35,7 +49,8 @@ export class LogViewer extends Table<LogEvent> { | ||||
|                     endIndex: this.logs?.length || 0, | ||||
|                 }, | ||||
|                 results: this.logs || [], | ||||
|         }; | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     renderEmpty(): TemplateResult { | ||||
|  | ||||
| @ -38,6 +38,10 @@ export abstract class ModelForm<T, PKT extends string | number> extends Form<T> | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     get instancePk(): PKT | undefined { | ||||
|         return this._instancePk; | ||||
|     } | ||||
|  | ||||
|     private _instancePk?: PKT; | ||||
|  | ||||
|     // Keep track if we've loaded the model instance | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	