From 07ed5e1cd95ed877139012e6b4cc13cb51431b48 Mon Sep 17 00:00:00 2001 From: Jens L Date: Tue, 30 Jan 2024 01:56:33 +0100 Subject: [PATCH] core: show all applications a user can access in admin interface (#8343) * core: show all applications a user can access in admin interface Signed-off-by: Jens Langhammer * minor adjustments Signed-off-by: Jens Langhammer * add relative time Signed-off-by: Jens Langhammer * use relative time in most places Signed-off-by: Jens Langhammer * improve admin dashboard scaling Signed-off-by: Jens Langhammer --------- Signed-off-by: Jens Langhammer --- authentik/brands/api.py | 6 ++ authentik/core/api/applications.py | 40 ++++++++-- schema.yml | 12 ++- .../admin/admin-overview/AdminOverviewPage.ts | 6 +- .../admin-overview/cards/RecentEventsCard.ts | 3 + .../admin/applications/ApplicationListPage.ts | 37 ++++----- web/src/admin/blueprints/BlueprintListPage.ts | 4 +- web/src/admin/brands/BrandListPage.ts | 4 +- .../enterprise/EnterpriseLicenseListPage.ts | 4 +- web/src/admin/groups/MemberSelectModal.ts | 7 +- web/src/admin/groups/RelatedUserList.ts | 7 +- .../policies/reputation/ReputationListPage.ts | 4 +- .../admin/system-tasks/SystemTaskListPage.ts | 4 +- web/src/admin/tokens/TokenListPage.ts | 6 +- web/src/admin/users/UserApplicationTable.ts | 79 +++++++++++++++++++ web/src/admin/users/UserListPage.ts | 15 ++-- web/src/admin/users/UserViewPage.ts | 22 +++++- .../notifications/NotificationDrawer.ts | 6 +- web/src/elements/oauth/UserAccessTokenList.ts | 6 +- .../elements/oauth/UserRefreshTokenList.ts | 6 +- web/src/elements/user/UserConsentList.ts | 6 +- .../user-settings/tokens/UserTokenList.ts | 10 ++- 22 files changed, 245 insertions(+), 49 deletions(-) create mode 100644 web/src/admin/users/UserApplicationTable.ts diff --git a/authentik/brands/api.py b/authentik/brands/api.py index 2b22a35089..eab725a4b3 100644 --- a/authentik/brands/api.py +++ b/authentik/brands/api.py @@ -11,6 +11,7 @@ from rest_framework.permissions import AllowAny from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ModelSerializer +from rest_framework.validators import UniqueValidator from rest_framework.viewsets import ModelViewSet from authentik.api.authorization import SecretKeyFilter @@ -57,6 +58,11 @@ class BrandSerializer(ModelSerializer): "web_certificate", "attributes", ] + extra_kwargs = { + # TODO: This field isn't unique on the database which is hard to backport + # hence we just validate the uniqueness here + "domain": {"validators": [UniqueValidator(Brand.objects.all())]}, + } class Themes(models.TextChoices): diff --git a/authentik/core/api/applications.py b/authentik/core/api/applications.py index 24313506fd..ee9ba8e3a3 100644 --- a/authentik/core/api/applications.py +++ b/authentik/core/api/applications.py @@ -1,4 +1,5 @@ """Application API Views""" +from copy import copy from datetime import timedelta from typing import Optional @@ -128,10 +129,16 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): queryset = backend().filter_queryset(self.request, queryset, self) return queryset - def _get_allowed_applications(self, queryset: QuerySet) -> list[Application]: + def _get_allowed_applications( + self, queryset: QuerySet, user: Optional[User] = None + ) -> list[Application]: applications = [] + request = self.request._request + if user: + request = copy(request) + request.user = user for application in queryset: - engine = PolicyEngine(application, self.request.user, self.request) + engine = PolicyEngine(application, request.user, request) engine.build() if engine.passing: applications.append(application) @@ -188,20 +195,43 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet): name="superuser_full_list", location=OpenApiParameter.QUERY, type=OpenApiTypes.BOOL, - ) + ), + OpenApiParameter( + name="for_user", + location=OpenApiParameter.QUERY, + type=OpenApiTypes.INT, + ), ] ) def list(self, request: Request) -> Response: """Custom list method that checks Policy based access instead of guardian""" - should_cache = request.GET.get("search", "") == "" + should_cache = request.query_params.get("search", "") == "" - superuser_full_list = str(request.GET.get("superuser_full_list", "false")).lower() == "true" + superuser_full_list = ( + str(request.query_params.get("superuser_full_list", "false")).lower() == "true" + ) if superuser_full_list and request.user.is_superuser: return super().list(request) queryset = self._filter_queryset_for_list(self.get_queryset()) self.paginate_queryset(queryset) + if "for_user" in request.query_params: + try: + for_user: int = int(request.query_params.get("for_user", 0)) + for_user = ( + get_objects_for_user(request.user, "authentik_core.view_user_applications") + .filter(pk=for_user) + .first() + ) + if not for_user: + raise ValidationError({"for_user": "User not found"}) + except ValueError as exc: + raise ValidationError from exc + allowed_applications = self._get_allowed_applications(queryset, user=for_user) + serializer = self.get_serializer(allowed_applications, many=True) + return self.get_paginated_response(serializer.data) + allowed_applications = [] if not should_cache: allowed_applications = self._get_allowed_applications(queryset) diff --git a/schema.yml b/schema.yml index 06c0a0d294..f9bc7b12fb 100644 --- a/schema.yml +++ b/schema.yml @@ -2658,6 +2658,10 @@ paths: operationId: core_applications_list description: Custom list method that checks Policy based access instead of guardian parameters: + - in: query + name: for_user + schema: + type: integer - in: query name: group schema: @@ -2931,8 +2935,14 @@ paths: schema: $ref: '#/components/schemas/PolicyTestResult' description: '' + '404': + description: for_user user not found '400': - description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' + description: '' '403': content: application/json: diff --git a/web/src/admin/admin-overview/AdminOverviewPage.ts b/web/src/admin/admin-overview/AdminOverviewPage.ts index 6b0ca4ba24..d24d17814d 100644 --- a/web/src/admin/admin-overview/AdminOverviewPage.ts +++ b/web/src/admin/admin-overview/AdminOverviewPage.ts @@ -79,7 +79,9 @@ export class AdminOverviewPage extends AKElement {
-
+
-
+
diff --git a/web/src/admin/admin-overview/cards/RecentEventsCard.ts b/web/src/admin/admin-overview/cards/RecentEventsCard.ts index 5ecfecd185..cbbf4382eb 100644 --- a/web/src/admin/admin-overview/cards/RecentEventsCard.ts +++ b/web/src/admin/admin-overview/cards/RecentEventsCard.ts @@ -47,6 +47,9 @@ export class RecentEventsCard extends Table { --pf-c-card__title--FontSize: var(--pf-global--FontSize--md); --pf-c-card__title--FontWeight: var(--pf-global--FontWeight--bold); } + * { + word-break: break-all; + } `, ); } diff --git a/web/src/admin/applications/ApplicationListPage.ts b/web/src/admin/applications/ApplicationListPage.ts index e74715e7c4..379522a4cb 100644 --- a/web/src/admin/applications/ApplicationListPage.ts +++ b/web/src/admin/applications/ApplicationListPage.ts @@ -9,7 +9,6 @@ import "@goauthentik/elements/buttons/SpinnerButton"; import "@goauthentik/elements/forms/DeleteBulkForm"; import "@goauthentik/elements/forms/ModalForm"; import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; -// import { getURLParam } from "@goauthentik/elements/router/RouteMatch"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { TableColumn } from "@goauthentik/elements/table/Table"; import { TablePage } from "@goauthentik/elements/table/TablePage"; @@ -25,6 +24,22 @@ import { Application, CoreApi } from "@goauthentik/api"; import "./ApplicationWizardHint"; +export const applicationListStyle = css` + /* Fix alignment issues with images in tables */ + .pf-c-table tbody > tr > * { + vertical-align: middle; + } + tr td:first-child { + width: auto; + min-width: 0px; + text-align: center; + vertical-align: middle; + } + .pf-c-sidebar.pf-m-gutter > .pf-c-sidebar__main > * + * { + margin-left: calc(var(--pf-c-sidebar__main--child--MarginLeft) / 2); + } +`; + @customElement("ak-application-list") export class ApplicationListPage extends TablePage { searchEnabled(): boolean { @@ -59,24 +74,7 @@ export class ApplicationListPage extends TablePage { } static get styles(): CSSResult[] { - return super.styles.concat( - PFCard, - css` - /* Fix alignment issues with images in tables */ - .pf-c-table tbody > tr > * { - vertical-align: middle; - } - tr td:first-child { - width: auto; - min-width: 0px; - text-align: center; - vertical-align: middle; - } - .pf-c-sidebar.pf-m-gutter > .pf-c-sidebar__main > * + * { - margin-left: calc(var(--pf-c-sidebar__main--child--MarginLeft) / 2); - } - `, - ); + return super.styles.concat(PFCard, applicationListStyle); } columns(): TableColumn[] { @@ -97,7 +95,6 @@ export class ApplicationListPage extends TablePage { renderSidebarAfter(): TemplateResult { // Rendering the wizard with .open here, as if we set the attribute in // renderObjectCreate() it'll open two wizards, since that function gets called twice - return html`
diff --git a/web/src/admin/blueprints/BlueprintListPage.ts b/web/src/admin/blueprints/BlueprintListPage.ts index 9fa587ded7..80b751d8af 100644 --- a/web/src/admin/blueprints/BlueprintListPage.ts +++ b/web/src/admin/blueprints/BlueprintListPage.ts @@ -1,4 +1,5 @@ import "@goauthentik/admin/blueprints/BlueprintForm"; +import { getRelativeTime } from "@goauthentik/app/common/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { uiConfig } from "@goauthentik/common/ui/config"; @@ -144,7 +145,8 @@ export class BlueprintListPage extends TablePage { html`
${item.name}
${description ? html`${description}` : html``}`, html`${BlueprintStatus(item)}`, - html`${item.lastApplied.toLocaleString()}`, + html`
${getRelativeTime(item.lastApplied)}
+ ${item.lastApplied.toLocaleString()}`, html``, html` ${msg("Update")} diff --git a/web/src/admin/brands/BrandListPage.ts b/web/src/admin/brands/BrandListPage.ts index bcf0e8ce25..9aa0e23c23 100644 --- a/web/src/admin/brands/BrandListPage.ts +++ b/web/src/admin/brands/BrandListPage.ts @@ -30,7 +30,7 @@ export class BrandListPage extends TablePage { return msg("Configure visual settings and defaults for different domains."); } pageIcon(): string { - return "pf-icon pf-icon-brand"; + return "pf-icon pf-icon-tenant"; } checkbox = true; @@ -51,6 +51,7 @@ export class BrandListPage extends TablePage { columns(): TableColumn[] { return [ new TableColumn(msg("Domain"), "domain"), + new TableColumn(msg("Brand name"), "branding_title"), new TableColumn(msg("Default?"), "default"), new TableColumn(msg("Actions")), ]; @@ -84,6 +85,7 @@ export class BrandListPage extends TablePage { row(item: Brand): TemplateResult[] { return [ html`${item.domain}`, + html`${item.brandingTitle}`, html``, html` ${msg("Update")} diff --git a/web/src/admin/enterprise/EnterpriseLicenseListPage.ts b/web/src/admin/enterprise/EnterpriseLicenseListPage.ts index 97790e7383..b8df8ca70f 100644 --- a/web/src/admin/enterprise/EnterpriseLicenseListPage.ts +++ b/web/src/admin/enterprise/EnterpriseLicenseListPage.ts @@ -1,4 +1,5 @@ import "@goauthentik/admin/enterprise/EnterpriseLicenseForm"; +import { getRelativeTime } from "@goauthentik/app/common/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; import { PFColor } from "@goauthentik/elements/Label"; @@ -202,7 +203,8 @@ export class EnterpriseLicenseListPage extends TablePage { subtext=${msg("Cumulative license expiry")} > ${this.summary?.hasLicense - ? this.summary.latestValid.toLocaleString() + ? html`
${getRelativeTime(this.summary.latestValid)}
+ ${this.summary.latestValid.toLocaleString()}` : "-"}
diff --git a/web/src/admin/groups/MemberSelectModal.ts b/web/src/admin/groups/MemberSelectModal.ts index f573644b21..29513403e5 100644 --- a/web/src/admin/groups/MemberSelectModal.ts +++ b/web/src/admin/groups/MemberSelectModal.ts @@ -1,6 +1,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; -import { first } from "@goauthentik/common/utils"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import "@goauthentik/elements/buttons/SpinnerButton"; import { PaginatedResponse } from "@goauthentik/elements/table/Table"; @@ -49,7 +49,10 @@ export class MemberSelectTable extends TableModal { html`
${item.username}
${item.name}`, html` `, - html`${first(item.lastLogin?.toLocaleString(), msg("-"))}`, + html`${item.lastLogin + ? html`
${getRelativeTime(item.lastLogin)}
+ ${item.lastLogin.toLocaleString()}` + : msg("-")}`, ]; } diff --git a/web/src/admin/groups/RelatedUserList.ts b/web/src/admin/groups/RelatedUserList.ts index f5a4f2081c..abde678fbe 100644 --- a/web/src/admin/groups/RelatedUserList.ts +++ b/web/src/admin/groups/RelatedUserList.ts @@ -7,7 +7,7 @@ import { me } from "@goauthentik/app/common/users"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { MessageLevel } from "@goauthentik/common/messages"; import { uiConfig } from "@goauthentik/common/ui/config"; -import { first } from "@goauthentik/common/utils"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; import { @@ -199,7 +199,10 @@ export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Tabl ${item.name} `, html``, - html`${first(item.lastLogin?.toLocaleString(), msg("-"))}`, + html`${item.lastLogin + ? html`
${getRelativeTime(item.lastLogin)}
+ ${item.lastLogin.toLocaleString()}` + : msg("-")}`, html` ${msg("Update")} ${msg("Update User")} diff --git a/web/src/admin/policies/reputation/ReputationListPage.ts b/web/src/admin/policies/reputation/ReputationListPage.ts index 2ff8254518..ab7ae28b79 100644 --- a/web/src/admin/policies/reputation/ReputationListPage.ts +++ b/web/src/admin/policies/reputation/ReputationListPage.ts @@ -1,3 +1,4 @@ +import { getRelativeTime } from "@goauthentik/app/common/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/elements/buttons/ModalButton"; @@ -92,7 +93,8 @@ export class ReputationListPage extends TablePage { : html``} ${item.ip}`, html`${item.score}`, - html`${item.updated.toLocaleString()}`, + html`
${getRelativeTime(item.updated)}
+ ${item.updated.toLocaleString()}`, html` { return [ html`${item.name}${item.uid ? `:${item.uid}` : ""}`, html`${item.description}`, - html`${item.finishTimestamp.toLocaleString()}`, + html`
${getRelativeTime(item.finishTimestamp)}
+ ${item.finishTimestamp.toLocaleString()}`, this.taskStatus(item), html` { : html``}`, html`${item.userObj?.username}`, html``, - html`${item.expiring ? item.expires?.toLocaleString() : msg("-")}`, + html`${item.expires + ? html`
${getRelativeTime(item.expires)}
+ ${item.expires.toLocaleString()}` + : msg("-")}`, html`${intentToLabel(item.intent ?? IntentEnum.Api)}`, html` ${!item.managed diff --git a/web/src/admin/users/UserApplicationTable.ts b/web/src/admin/users/UserApplicationTable.ts new file mode 100644 index 0000000000..c86e53d78b --- /dev/null +++ b/web/src/admin/users/UserApplicationTable.ts @@ -0,0 +1,79 @@ +import { applicationListStyle } from "@goauthentik/app/admin/applications/ApplicationListPage"; +import { DEFAULT_CONFIG } from "@goauthentik/app/common/api/config"; +import { uiConfig } from "@goauthentik/app/common/ui/config"; +import { PFSize } from "@goauthentik/app/elements/Spinner"; +import { PaginatedResponse, Table, TableColumn } from "@goauthentik/app/elements/table/Table"; +import "@goauthentik/components/ak-app-icon"; +import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; + +import { msg } from "@lit/localize"; +import { CSSResult, TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +import { Application, CoreApi, User } from "@goauthentik/api"; + +@customElement("ak-user-application-table") +export class UserApplicationTable extends Table { + @property({ attribute: false }) + user?: User; + + static get styles(): CSSResult[] { + return super.styles.concat(applicationListStyle); + } + + async apiEndpoint(page: number): Promise> { + return new CoreApi(DEFAULT_CONFIG).coreApplicationsList({ + forUser: this.user?.pk, + page: page, + pageSize: (await uiConfig()).pagination.perPage, + ordering: this.order, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn(""), + new TableColumn(msg("Name"), "name"), + new TableColumn(msg("Group"), "group"), + new TableColumn(msg("Provider")), + new TableColumn(msg("Provider Type")), + new TableColumn(msg("Actions")), + ]; + } + + row(item: Application): TemplateResult[] { + return [ + html``, + html` +
${item.name}
+ ${item.metaPublisher ? html`${item.metaPublisher}` : html``} +
`, + html`${item.group || msg("-")}`, + item.provider + ? html` + ${item.providerObj?.name} + ` + : html`-`, + html`${item.providerObj?.verboseName || msg("-")}`, + html` + ${msg("Update")} + ${msg("Update Application")} + + + + + ${item.launchUrl + ? html` + + + + ` + : html``}`, + ]; + } +} diff --git a/web/src/admin/users/UserListPage.ts b/web/src/admin/users/UserListPage.ts index 40f4639122..e05e7700e1 100644 --- a/web/src/admin/users/UserListPage.ts +++ b/web/src/admin/users/UserListPage.ts @@ -9,7 +9,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { userTypeToLabel } from "@goauthentik/common/labels"; import { MessageLevel } from "@goauthentik/common/messages"; import { DefaultUIConfig, uiConfig } from "@goauthentik/common/ui/config"; -import { first } from "@goauthentik/common/utils"; +import { getRelativeTime } from "@goauthentik/common/utils"; import "@goauthentik/components/ak-status-label"; import { rootInterface } from "@goauthentik/elements/Base"; import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider"; @@ -159,6 +159,7 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa new TableColumn(msg("Name"), "username"), new TableColumn(msg("Active"), "is_active"), new TableColumn(msg("Last login"), "last_login"), + new TableColumn(msg("Type"), "type"), new TableColumn(msg("Actions")), ]; } @@ -246,11 +247,15 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa this.can(CapabilitiesEnum.CanImpersonate) && item.pk !== this.me?.user.pk; return [ html` -
${item.username}
- ${item.name === "" ? msg("") : item.name}
 ${userTypeToLabel(item.type)}`, +
${item.username}
+ ${item.name === "" ? msg("") : item.name} + `, html``, - html`${first(item.lastLogin?.toLocaleString(), msg("-"))}`, + html`${item.lastLogin + ? html`
${getRelativeTime(item.lastLogin)}
+ ${item.lastLogin.toLocaleString()}` + : msg("-")}`, + html`${userTypeToLabel(item.type)}`, html` ${msg("Update")} ${msg("Update User")} diff --git a/web/src/admin/users/UserViewPage.ts b/web/src/admin/users/UserViewPage.ts index 4374028abc..f270f6dfdf 100644 --- a/web/src/admin/users/UserViewPage.ts +++ b/web/src/admin/users/UserViewPage.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/groups/RelatedGroupList"; import "@goauthentik/admin/users/UserActiveForm"; +import "@goauthentik/admin/users/UserApplicationTable"; import "@goauthentik/admin/users/UserChart"; import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserPasswordForm"; @@ -8,6 +9,7 @@ import { requestRecoveryLink, } from "@goauthentik/app/admin/users/UserListPage"; import { me } from "@goauthentik/app/common/users"; +import { getRelativeTime } from "@goauthentik/app/common/utils"; import "@goauthentik/app/elements/oauth/UserAccessTokenList"; import "@goauthentik/app/elements/oauth/UserRefreshTokenList"; import "@goauthentik/app/elements/rbac/ObjectPermissionsPage"; @@ -147,7 +149,10 @@ export class UserViewPage extends WithCapabilitiesConfig(AKElement) { [msg("Username"), user.username], [msg("Name"), user.name], [msg("Email"), user.email || "-"], - [msg("Last login"), user.lastLogin?.toLocaleString()], + [msg("Last login"), user.lastLogin + ? html`
${getRelativeTime(user.lastLogin)}
+ ${user.lastLogin.toLocaleString()}` + : html`${msg("-")}`], [msg("Active"), html``], [msg("Type"), userTypeToLabel(user.type)], [msg("Superuser"), html``], @@ -317,6 +322,14 @@ export class UserViewPage extends WithCapabilitiesConfig(AKElement) { `; } + renderTabApplications(user: User): TemplateResult { + return html`
+
+ +
+
`; + } + renderBody() { if (!this.user) { return nothing; @@ -399,6 +412,13 @@ export class UserViewPage extends WithCapabilitiesConfig(AKElement) {
${this.renderTabCredentialsToken(this.user)}
+
+ ${this.renderTabApplications(this.user)} +

${item.body}

${item.created?.toLocaleString()} + ${getRelativeTime(item.created!)} + `; } diff --git a/web/src/elements/oauth/UserAccessTokenList.ts b/web/src/elements/oauth/UserAccessTokenList.ts index 3ae870a197..f6bfb38d4f 100644 --- a/web/src/elements/oauth/UserAccessTokenList.ts +++ b/web/src/elements/oauth/UserAccessTokenList.ts @@ -1,3 +1,4 @@ +import { getRelativeTime } from "@goauthentik/app/common/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/components/ak-status-label"; @@ -86,7 +87,10 @@ export class UserOAuthAccessTokenList extends Table { return [ html` ${item.provider?.name} `, html``, - html`${item.expires?.toLocaleString()}`, + html`${item.expires + ? html`
${getRelativeTime(item.expires)}
+ ${item.expires.toLocaleString()}` + : msg("-")}`, html`${item.scope.join(", ")}`, ]; } diff --git a/web/src/elements/oauth/UserRefreshTokenList.ts b/web/src/elements/oauth/UserRefreshTokenList.ts index 9069472350..7ea20f47ac 100644 --- a/web/src/elements/oauth/UserRefreshTokenList.ts +++ b/web/src/elements/oauth/UserRefreshTokenList.ts @@ -1,3 +1,4 @@ +import { getRelativeTime } from "@goauthentik/app/common/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/components/ak-status-label"; @@ -87,7 +88,10 @@ export class UserOAuthRefreshTokenList extends Table { return [ html` ${item.provider?.name} `, html``, - html`${item.expires?.toLocaleString()}`, + html`${item.expires + ? html`
${getRelativeTime(item.expires)}
+ ${item.expires.toLocaleString()}` + : msg("-")}`, html`${item.scope.join(", ")}`, ]; } diff --git a/web/src/elements/user/UserConsentList.ts b/web/src/elements/user/UserConsentList.ts index 22e2bb4636..96e488c9d0 100644 --- a/web/src/elements/user/UserConsentList.ts +++ b/web/src/elements/user/UserConsentList.ts @@ -1,3 +1,4 @@ +import { getRelativeTime } from "@goauthentik/app/common/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { uiConfig } from "@goauthentik/common/ui/config"; import "@goauthentik/elements/forms/DeleteBulkForm"; @@ -61,7 +62,10 @@ export class UserConsentList extends Table { row(item: UserConsent): TemplateResult[] { return [ html`${item.application.name}`, - html`${item.expires?.toLocaleString()}`, + html`${item.expires + ? html`
${getRelativeTime(item.expires)}
+ ${item.expires.toLocaleString()}` + : msg("-")}`, html`${item.permissions || "-"}`, ]; } diff --git a/web/src/user/user-settings/tokens/UserTokenList.ts b/web/src/user/user-settings/tokens/UserTokenList.ts index 2760ea16d1..2b5015e15f 100644 --- a/web/src/user/user-settings/tokens/UserTokenList.ts +++ b/web/src/user/user-settings/tokens/UserTokenList.ts @@ -1,3 +1,4 @@ +import { getRelativeTime } from "@goauthentik/app/common/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { intentToLabel } from "@goauthentik/common/labels"; import { uiConfig } from "@goauthentik/common/ui/config"; @@ -108,7 +109,14 @@ export class UserTokenList extends Table {
- ${item.expiring ? item.expires?.toLocaleString() : msg("-")} + ${item.expiring + ? html` + ${getRelativeTime(item.expires!)} + ` + : msg("-")}