web: migrate Token List to web
This commit is contained in:
parent
fd28f37c0d
commit
6597d5bd28
|
@ -1,102 +0,0 @@
|
|||
{% extends "administration/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load authentik_utils %}
|
||||
|
||||
{% block content %}
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>
|
||||
<i class="pf-icon pf-icon-security"></i>
|
||||
{% trans 'Tokens' %}
|
||||
</h1>
|
||||
<p>{% trans "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access." %}</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
{% if object_list %}
|
||||
<div class="pf-c-toolbar">
|
||||
<div class="pf-c-toolbar__content">
|
||||
{% include 'partials/toolbar_search.html' %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
|
||||
<th role="columnheader" scope="col">{% trans 'User' %}</th>
|
||||
<th role="columnheader" scope="col">{% trans 'Expires?' %}</th>
|
||||
<th role="columnheader" scope="col">{% trans 'Expiry Date' %}</th>
|
||||
<th role="cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody role="rowgroup">
|
||||
{% for token in object_list %}
|
||||
<tr role="row">
|
||||
<th role="columnheader">
|
||||
<div>{{ token.identifier }}</div>
|
||||
</th>
|
||||
<td role="cell">
|
||||
<span>
|
||||
{{ token.user }}
|
||||
</span>
|
||||
</td>
|
||||
<td role="cell">
|
||||
<span>
|
||||
{{ token.expiring|yesno:"Yes,No" }}
|
||||
</span>
|
||||
</td>
|
||||
<td role="cell">
|
||||
<span>
|
||||
{% if not token.expiring %}
|
||||
-
|
||||
{% else %}
|
||||
{{ token.expires }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<ak-modal-button href="{% url 'authentik_admin:token-delete' pk=token.pk %}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||
{% trans 'Delete' %}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-token-copy-button identifier="{{ token.identifier }}">
|
||||
{% trans 'Copy token' %}
|
||||
</ak-token-copy-button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pf-c-pagination pf-m-bottom">
|
||||
{% include 'partials/pagination.html' %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="pf-c-toolbar">
|
||||
<div class="pf-c-toolbar__content">
|
||||
{% include 'partials/toolbar_search.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-empty-state">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<i class="fas fa-key pf-c-empty-state__icon" aria-hidden="true"></i>
|
||||
<h1 class="pf-c-title pf-m-lg">
|
||||
{% trans 'No Tokens.' %}
|
||||
</h1>
|
||||
<div class="pf-c-empty-state__body">
|
||||
{% if request.GET.search != "" %}
|
||||
{% trans "Your search query doesn't match any token." %}
|
||||
{% else %}
|
||||
{% trans 'Currently no tokens exist.' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -53,7 +53,6 @@ urlpatterns = [
|
|||
name="application-delete",
|
||||
),
|
||||
# Tokens
|
||||
path("tokens/", tokens.TokenListView.as_view(), name="tokens"),
|
||||
path(
|
||||
"tokens/<uuid:pk>/delete/",
|
||||
tokens.TokenDeleteView.as_view(),
|
||||
|
|
|
@ -1,39 +1,12 @@
|
|||
"""authentik Token administration"""
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import ListView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
from guardian.mixins import PermissionRequiredMixin
|
||||
|
||||
from authentik.admin.views.utils import (
|
||||
DeleteMessageView,
|
||||
SearchListMixin,
|
||||
UserPaginateListMixin,
|
||||
)
|
||||
from authentik.admin.views.utils import DeleteMessageView
|
||||
from authentik.core.models import Token
|
||||
|
||||
|
||||
class TokenListView(
|
||||
LoginRequiredMixin,
|
||||
PermissionListMixin,
|
||||
UserPaginateListMixin,
|
||||
SearchListMixin,
|
||||
ListView,
|
||||
):
|
||||
"""Show list of all tokens"""
|
||||
|
||||
model = Token
|
||||
permission_required = "authentik_core.view_token"
|
||||
ordering = "expires"
|
||||
template_name = "administration/token/list.html"
|
||||
search_fields = [
|
||||
"identifier",
|
||||
"intent",
|
||||
"user__username",
|
||||
"description",
|
||||
]
|
||||
|
||||
|
||||
class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView):
|
||||
"""Delete token"""
|
||||
|
||||
|
@ -41,5 +14,5 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
|
|||
permission_required = "authentik_core.delete_token"
|
||||
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("authentik_admin:tokens")
|
||||
success_url = "/"
|
||||
success_message = _("Successfully deleted Token")
|
||||
|
|
|
@ -7,19 +7,14 @@ from django.contrib.auth.mixins import (
|
|||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.response import HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.shortcuts import redirect, reverse
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import DetailView, UpdateView
|
||||
from guardian.mixins import (
|
||||
PermissionRequiredMixin,
|
||||
)
|
||||
from guardian.mixins import PermissionRequiredMixin
|
||||
|
||||
from authentik.admin.forms.users import UserForm
|
||||
from authentik.admin.views.utils import (
|
||||
BackSuccessUrlMixin,
|
||||
DeleteMessageView,
|
||||
)
|
||||
from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
|
||||
from authentik.core.models import Token, User
|
||||
from authentik.lib.views import CreateAssignPermView
|
||||
|
||||
|
|
|
@ -19,3 +19,5 @@ class GroupViewSet(ModelViewSet):
|
|||
|
||||
queryset = Group.objects.all()
|
||||
serializer_class = GroupSerializer
|
||||
search_fields = ["name", "is_superuser"]
|
||||
filterset_fields = ["name", "is_superuser"]
|
||||
|
|
|
@ -9,6 +9,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.serializers import ModelSerializer, Serializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.users import UserSerializer
|
||||
from authentik.core.models import Token
|
||||
from authentik.events.models import Event, EventAction
|
||||
|
||||
|
@ -16,10 +17,21 @@ from authentik.events.models import Event, EventAction
|
|||
class TokenSerializer(ModelSerializer):
|
||||
"""Token Serializer"""
|
||||
|
||||
user = UserSerializer()
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Token
|
||||
fields = ["pk", "identifier", "intent", "user", "description"]
|
||||
fields = [
|
||||
"pk",
|
||||
"identifier",
|
||||
"intent",
|
||||
"user",
|
||||
"description",
|
||||
"expires",
|
||||
"expiring",
|
||||
]
|
||||
depth = 2
|
||||
|
||||
|
||||
class TokenViewSerializer(Serializer):
|
||||
|
@ -40,6 +52,19 @@ class TokenViewSet(ModelViewSet):
|
|||
lookup_field = "identifier"
|
||||
queryset = Token.filter_not_expired()
|
||||
serializer_class = TokenSerializer
|
||||
search_fields = [
|
||||
"identifier",
|
||||
"intent",
|
||||
"user__username",
|
||||
"description",
|
||||
]
|
||||
filterset_fields = [
|
||||
"identifier",
|
||||
"intent",
|
||||
"user__username",
|
||||
"description",
|
||||
]
|
||||
ordering = ["expires"]
|
||||
|
||||
@swagger_auto_schema(responses={200: TokenViewSerializer(many=False)})
|
||||
@action(detail=True)
|
||||
|
|
|
@ -37,6 +37,7 @@ class UserSerializer(ModelSerializer):
|
|||
"is_superuser",
|
||||
"email",
|
||||
"avatar",
|
||||
"attributes",
|
||||
]
|
||||
|
||||
|
||||
|
@ -45,6 +46,8 @@ class UserViewSet(ModelViewSet):
|
|||
|
||||
queryset = User.objects.none()
|
||||
serializer_class = UserSerializer
|
||||
search_fields = ["username", "name", "is_active"]
|
||||
filterset_fields = ["username", "name", "is_active"]
|
||||
|
||||
def get_queryset(self):
|
||||
return User.objects.all().exclude(pk=get_anonymous_user().pk)
|
||||
|
|
|
@ -186,6 +186,8 @@ class StageViewSet(ReadOnlyModelViewSet):
|
|||
|
||||
queryset = Stage.objects.all()
|
||||
serializer_class = StageSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
return Stage.objects.select_subclasses()
|
||||
|
|
|
@ -61,6 +61,8 @@ class ServiceConnectionViewSet(ModelViewSet):
|
|||
|
||||
queryset = OutpostServiceConnection.objects.select_subclasses()
|
||||
serializer_class = ServiceConnectionSerializer
|
||||
search_fields = ["name"]
|
||||
filterset_fields = ["name"]
|
||||
|
||||
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||
@action(detail=False)
|
||||
|
|
|
@ -107,6 +107,7 @@ class PolicyViewSet(ReadOnlyModelViewSet):
|
|||
"bindings": ["isnull"],
|
||||
"promptstage": ["isnull"],
|
||||
}
|
||||
search_fields = ["name"]
|
||||
|
||||
def get_queryset(self):
|
||||
return Policy.objects.select_subclasses().prefetch_related(
|
||||
|
|
172
swagger.yaml
172
swagger.yaml
|
@ -309,6 +309,16 @@ paths:
|
|||
operationId: core_groups_list
|
||||
description: Group Viewset
|
||||
parameters:
|
||||
- name: name
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: is_superuser
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -436,6 +446,26 @@ paths:
|
|||
operationId: core_tokens_list
|
||||
description: Token Viewset
|
||||
parameters:
|
||||
- name: identifier
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: intent
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: user__username
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: description
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -582,6 +612,21 @@ paths:
|
|||
operationId: core_users_list
|
||||
description: User Viewset
|
||||
parameters:
|
||||
- name: username
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: name
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: is_active
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -649,6 +694,21 @@ paths:
|
|||
operationId: core_users_me
|
||||
description: Get information about current user
|
||||
parameters:
|
||||
- name: username
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: name
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: is_active
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -2107,6 +2167,11 @@ paths:
|
|||
operationId: outposts_service_connections_all_list
|
||||
description: ServiceConnection Viewset
|
||||
parameters:
|
||||
- name: name
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -2174,6 +2239,11 @@ paths:
|
|||
operationId: outposts_service_connections_all_types
|
||||
description: Get all creatable service connection types
|
||||
parameters:
|
||||
- name: name
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -5506,6 +5576,11 @@ paths:
|
|||
operationId: stages_all_list
|
||||
description: Stage Viewset
|
||||
parameters:
|
||||
- name: name
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -5557,6 +5632,11 @@ paths:
|
|||
operationId: stages_all_types
|
||||
description: Get all creatable stage types
|
||||
parameters:
|
||||
- name: name
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -8091,48 +8171,8 @@ definitions:
|
|||
attributes:
|
||||
title: Attributes
|
||||
type: object
|
||||
Token:
|
||||
description: Token Serializer
|
||||
required:
|
||||
- identifier
|
||||
- user
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
title: Token uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
identifier:
|
||||
title: Identifier
|
||||
type: string
|
||||
format: slug
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
intent:
|
||||
title: Intent
|
||||
type: string
|
||||
enum:
|
||||
- verification
|
||||
- api
|
||||
- recovery
|
||||
user:
|
||||
title: User
|
||||
type: integer
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
TokenView:
|
||||
description: Show token's current key
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
title: Key
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
User:
|
||||
title: User
|
||||
description: User Serializer
|
||||
required:
|
||||
- username
|
||||
|
@ -8179,6 +8219,56 @@ definitions:
|
|||
title: Avatar
|
||||
type: string
|
||||
readOnly: true
|
||||
attributes:
|
||||
title: Attributes
|
||||
type: object
|
||||
Token:
|
||||
description: Token Serializer
|
||||
required:
|
||||
- identifier
|
||||
- user
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
title: Token uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
identifier:
|
||||
title: Identifier
|
||||
type: string
|
||||
format: slug
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
intent:
|
||||
title: Intent
|
||||
type: string
|
||||
enum:
|
||||
- verification
|
||||
- api
|
||||
- recovery
|
||||
user:
|
||||
$ref: '#/definitions/User'
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
expires:
|
||||
title: Expires
|
||||
type: string
|
||||
format: date-time
|
||||
expiring:
|
||||
title: Expiring
|
||||
type: boolean
|
||||
TokenView:
|
||||
description: Show token's current key
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
title: Key
|
||||
type: string
|
||||
readOnly: true
|
||||
minLength: 1
|
||||
CertificateKeyPair:
|
||||
description: CertificateKeyPair Serializer
|
||||
required:
|
||||
|
|
|
@ -1,11 +1,43 @@
|
|||
import { DefaultClient } from "./Client";
|
||||
import { AKResponse, DefaultClient, QueryArguments } from "./Client";
|
||||
import { User } from "./Users";
|
||||
|
||||
interface TokenResponse {
|
||||
key: string;
|
||||
export enum TokenIntent {
|
||||
INTENT_VERIFICATION = "verification",
|
||||
INTENT_API = "api",
|
||||
INTENT_RECOVERY = "recovery",
|
||||
}
|
||||
|
||||
export function tokenByIdentifier(identifier: string): Promise<string> {
|
||||
return DefaultClient.fetch<TokenResponse>(["core", "tokens", identifier, "view_key"]).then(
|
||||
(r) => r.key
|
||||
);
|
||||
export class Token {
|
||||
|
||||
pk: string;
|
||||
identifier: string;
|
||||
intent: TokenIntent;
|
||||
user: User;
|
||||
description: string;
|
||||
|
||||
expires: number;
|
||||
expiring: boolean;
|
||||
|
||||
constructor() {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
static get(pk: string): Promise<User> {
|
||||
return DefaultClient.fetch<User>(["core", "tokens", pk]);
|
||||
}
|
||||
|
||||
static list(filter?: QueryArguments): Promise<AKResponse<Token>> {
|
||||
return DefaultClient.fetch<AKResponse<Token>>(["core", "tokens"], filter);
|
||||
}
|
||||
|
||||
static adminUrl(rest: string): string {
|
||||
return `/administration/tokens/${rest}`;
|
||||
}
|
||||
|
||||
static getKey(identifier: string): Promise<string> {
|
||||
return DefaultClient.fetch<{ key: string }>(["core", "tokens", identifier, "view_key"]).then(
|
||||
(r) => r.key
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
|
|||
import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
|
||||
// @ts-ignore
|
||||
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
|
||||
import { tokenByIdentifier } from "../../api/Tokens";
|
||||
import { Token } from "../../api/Tokens";
|
||||
import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||
|
||||
@customElement("ak-token-copy-button")
|
||||
|
@ -35,7 +35,7 @@ export class TokenCopyButton extends LitElement {
|
|||
}, 1500);
|
||||
return;
|
||||
}
|
||||
tokenByIdentifier(this.identifier).then((token) => {
|
||||
Token.getKey(this.identifier).then((token) => {
|
||||
navigator.clipboard.writeText(token).then(() => {
|
||||
this.buttonClass = SUCCESS_CLASS;
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -43,7 +43,7 @@ export class TablePagination extends LitElement {
|
|||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
@click=${() => { this.pageChangeHandler(this.pages?.next || 0); }}
|
||||
?disabled="${(this.pages?.next || 0) < 0}"
|
||||
?disabled="${(this.pages?.next || 0) <= 0}"
|
||||
aria-label="${gettext("Go to next page")}"
|
||||
>
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
|
|
|
@ -50,7 +50,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
|
|||
new SidebarItem("User", "/users"),
|
||||
new SidebarItem("Groups", "/groups"),
|
||||
new SidebarItem("Certificates", "/crypto/certificates"),
|
||||
new SidebarItem("Tokens", "/administration/tokens/"),
|
||||
new SidebarItem("Tokens", "/tokens"),
|
||||
).when((): Promise<boolean> => {
|
||||
return User.me().then(u => u.is_superuser);
|
||||
}),
|
||||
|
|
67
web/src/pages/tokens/TokenListPage.ts
Normal file
67
web/src/pages/tokens/TokenListPage.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { gettext } from "django";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { AKResponse } from "../../api/Client";
|
||||
import { TablePage } from "../../elements/table/TablePage";
|
||||
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/Dropdown";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { Token } from "../../api/Tokens";
|
||||
|
||||
@customElement("ak-token-list")
|
||||
export class TokenListPage extends TablePage<Token> {
|
||||
searchEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
pageTitle(): string {
|
||||
return gettext("Tokens");
|
||||
}
|
||||
pageDescription(): string {
|
||||
return gettext("Tokens are used throughout authentik for Email validation stages, Recovery keys and API access.");
|
||||
}
|
||||
pageIcon(): string {
|
||||
return gettext("pf-icon pf-icon-security");
|
||||
}
|
||||
|
||||
@property()
|
||||
order = "expires";
|
||||
|
||||
apiEndpoint(page: number): Promise<AKResponse<Token>> {
|
||||
return Token.list({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn("Identifier", "identifier"),
|
||||
new TableColumn("User", "user"),
|
||||
new TableColumn("Expires?", "expiring"),
|
||||
new TableColumn("Expiry date", "expires"),
|
||||
new TableColumn(""),
|
||||
];
|
||||
}
|
||||
|
||||
row(item: Token): TemplateResult[] {
|
||||
return [
|
||||
html`${item.identifier}`,
|
||||
html`${item.user.username}`,
|
||||
html`${item.expiring ? "Yes" : "No"}`,
|
||||
html`${item.expiring ? new Date(item.expires * 1000).toLocaleString() : '-'}`,
|
||||
html`
|
||||
<ak-modal-button href="${Token.adminUrl(`${item.pk}/delete/`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||
${gettext('Delete')}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-token-copy-button identifier="${item.identifier}">
|
||||
${gettext('Copy Key')}
|
||||
</ak-token-copy-button>
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ import "./pages/sources/SourcesListPage";
|
|||
import "./pages/sources/SourceViewPage";
|
||||
import "./pages/groups/GroupListPage";
|
||||
import "./pages/users/UserListPage";
|
||||
import "./pages/tokens/TokenListPage";
|
||||
import "./pages/system-tasks/SystemTaskListPage";
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
|
@ -47,6 +48,7 @@ export const ROUTES: Route[] = [
|
|||
new Route(new RegExp("^/groups$"), html`<ak-group-list></ak-group-list>`),
|
||||
new Route(new RegExp("^/users$"), html`<ak-user-list></ak-user-list>`),
|
||||
new Route(new RegExp("^/flows$"), html`<ak-flow-list></ak-flow-list>`),
|
||||
new Route(new RegExp("^/tokens$"), html`<ak-token-list></ak-token-list>`),
|
||||
new Route(new RegExp(`^/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => {
|
||||
return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;
|
||||
}),
|
||||
|
|
Reference in a new issue