web: migrate remaining list views to web

This commit is contained in:
Jens Langhammer 2021-02-20 00:09:53 +01:00
parent 9d4c22c706
commit 854d94056e
17 changed files with 606 additions and 397 deletions

View File

@ -1,109 +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-migration"></i>
{% trans 'Invitations' %}
</h1>
<p>{% trans "Create Invitation Links to enroll Users, and optionally force specific attributes of their account." %}
</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' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% 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 'ID' %}</th>
<th role="columnheader" scope="col">{% trans 'Created by' %}</th>
<th role="columnheader" scope="col">{% trans 'Expiry' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for invitation in object_list %}
<tr role="row">
<td role="cell">
<span>
{{ invitation.invite_uuid }}
</span>
</td>
<td role="cell">
<span>
{{ invitation.created_by }}
</span>
</td>
<td role="cell">
<span>
{{ invitation.expiry|default:"-" }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-delete' pk=invitation.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-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="pf-icon pf-icon-migration pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Invitations.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any invitations." %}
{% else %}
{% trans 'Currently no invitations exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-modal-button href="{% url 'authentik_admin:stage-invitation-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -1,125 +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-plugged"></i>
{% trans 'Prompts' %}
</h1>
<p>{% trans "Single Prompts that can be used for Prompt Stages." %}</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' %}
<div class="pf-c-toolbar__bulk-select">
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-create' %}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
{% trans 'Create' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% 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 'Field' %}</th>
<th role="columnheader" scope="col">{% trans 'Label' %}</th>
<th role="columnheader" scope="col">{% trans 'Type' %}</th>
<th role="columnheader" scope="col">{% trans 'Order' %}</th>
<th role="columnheader" scope="col">{% trans 'Flows' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for prompt in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ prompt.field_key }}</div>
</div>
</th>
<td role="cell">
<div>
{{ prompt.label }}
</div>
</td>
<td role="cell">
<div>
{{ prompt.type }}
</div>
</td>
<td role="cell">
<div>
{{ prompt.order }}
</div>
</td>
<td role="cell">
<ul>
{% for flow in prompt.flow_set.all %}
<li>{{ flow.slug }}</li>
{% empty %}
<li>-</li>
{% endfor %}
</ul>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-update' pk=prompt.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Update' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:stage-prompt-delete' pk=prompt.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
{% trans 'Delete' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-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="pf-icon pf-icon-plugged pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Stage Prompts.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any stage prompts." %}
{% else %}
{% trans 'Currently no stage prompts exist. Click the button below to create one.' %}
{% endif %}
</div>
<a href="{% url 'authentik_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -153,11 +153,6 @@ urlpatterns = [
name="stage-binding-delete",
),
# Stage Prompts
path(
"stages_prompts/",
stages_prompts.PromptListView.as_view(),
name="stage-prompts",
),
path(
"stages_prompts/create/",
stages_prompts.PromptCreateView.as_view(),
@ -174,11 +169,6 @@ urlpatterns = [
name="stage-prompt-delete",
),
# Stage Invitations
path(
"stages/invitations/",
stages_invitations.InvitationListView.as_view(),
name="stage-invitations",
),
path(
"stages/invitations/create/",
stages_invitations.InvitationCreateView.as_view(),

View File

@ -5,37 +5,15 @@ from django.contrib.auth.mixins import (
)
from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseRedirect
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.lib.views import CreateAssignPermView
from authentik.stages.invitation.forms import InvitationForm
from authentik.stages.invitation.models import Invitation
class InvitationListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all invitations"""
model = Invitation
permission_required = "authentik_stages_invitation.view_invitation"
template_name = "administration/stage_invitation/list.html"
ordering = "-expires"
search_fields = ["created_by__username", "expires", "fixed_data"]
class InvitationCreateView(
SuccessMessageMixin,
LoginRequiredMixin,
@ -49,7 +27,7 @@ class InvitationCreateView(
permission_required = "authentik_stages_invitation.add_invitation"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-invitations")
success_url = "/"
success_message = _("Successfully created Invitation")
def form_valid(self, form):
@ -68,5 +46,5 @@ class InvitationDeleteView(
permission_required = "authentik_stages_invitation.delete_invitation"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-invitations")
success_url = "/"
success_message = _("Successfully deleted Invitation")

View File

@ -4,42 +4,16 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import (
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.admin.views.utils import DeleteMessageView
from authentik.lib.views import CreateAssignPermView
from authentik.stages.prompt.forms import PromptAdminForm
from authentik.stages.prompt.models import Prompt
class PromptListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all prompts"""
model = Prompt
permission_required = "authentik_stages_prompt.view_prompt"
ordering = "order"
template_name = "administration/stage_prompt/list.html"
search_fields = [
"field_key",
"label",
"type",
"placeholder",
]
class PromptCreateView(
SuccessMessageMixin,
LoginRequiredMixin,
@ -53,7 +27,7 @@ class PromptCreateView(
permission_required = "authentik_stages_prompt.add_prompt"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = "/"
success_message = _("Successfully created Prompt")
@ -70,7 +44,7 @@ class PromptUpdateView(
permission_required = "authentik_stages_prompt.change_prompt"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = "/"
success_message = _("Successfully updated Prompt")
@ -81,5 +55,5 @@ class PromptDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
permission_required = "authentik_stages_prompt.delete_prompt"
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:stage-prompts")
success_url = "/"
success_message = _("Successfully deleted Prompt")

View File

@ -136,8 +136,8 @@ router.register("stages/captcha", CaptchaStageViewSet)
router.register("stages/consent", ConsentStageViewSet)
router.register("stages/email", EmailStageViewSet)
router.register("stages/identification", IdentificationStageViewSet)
router.register("stages/invitation", InvitationStageViewSet)
router.register("stages/invitation/invitations", InvitationViewSet)
router.register("stages/invitation/stages", InvitationStageViewSet)
router.register("stages/password", PasswordStageViewSet)
router.register("stages/prompt/prompts", PromptViewSet)
router.register("stages/prompt/stages", PromptStageViewSet)

View File

@ -23,6 +23,7 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]:
"verbose_name_plural",
"object_type",
"flow_set",
"promptstage_set",
)
for to_remove_name in to_remove:
if to_remove_name in data:

View File

@ -34,7 +34,9 @@ class InvitationSerializer(ModelSerializer):
"pk",
"expires",
"fixed_data",
"created_by",
]
depth = 2
class InvitationViewSet(ModelViewSet):
@ -42,6 +44,9 @@ class InvitationViewSet(ModelViewSet):
queryset = Invitation.objects.all()
serializer_class = InvitationSerializer
order = ["-expires"]
search_fields = ["created_by__username", "expires"]
filterset_fields = ["created_by__username", "expires"]
def perform_create(self, serializer: InvitationSerializer):
serializer.instance.created_by = self.request.user

View File

@ -31,6 +31,8 @@ class PromptStageViewSet(ModelViewSet):
class PromptSerializer(ModelSerializer):
"""Prompt Serializer"""
promptstage_set = StageSerializer(many=True, required=False)
class Meta:
model = Prompt
@ -42,11 +44,13 @@ class PromptSerializer(ModelSerializer):
"required",
"placeholder",
"order",
"promptstage_set",
]
class PromptViewSet(ModelViewSet):
"""Prompt Viewset"""
queryset = Prompt.objects.all()
queryset = Prompt.objects.all().prefetch_related("promptstage_set")
serializer_class = PromptSerializer
filterset_fields = ["field_key", "label", "type", "placeholder"]

View File

@ -6830,78 +6830,21 @@ paths:
required: true
type: string
format: uuid
/stages/invitation/:
get:
operationId: stages_invitation_list
description: InvitationStage Viewset
parameters:
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: A page number within the paginated result set.
required: false
type: integer
- name: page_size
in: query
description: Number of results to return per page.
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- count
- results
type: object
properties:
count:
type: integer
next:
type: string
format: uri
x-nullable: true
previous:
type: string
format: uri
x-nullable: true
results:
type: array
items:
$ref: '#/definitions/InvitationStage'
tags:
- stages
post:
operationId: stages_invitation_create
description: InvitationStage Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/InvitationStage'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/InvitationStage'
tags:
- stages
parameters: []
/stages/invitation/invitations/:
get:
operationId: stages_invitation_invitations_list
description: Invitation Viewset
parameters:
- name: created_by__username
in: query
description: ''
required: false
type: string
- name: expires
in: query
description: ''
required: false
type: string
- name: ordering
in: query
description: Which field to use when ordering the results.
@ -7024,9 +6967,76 @@ paths:
required: true
type: string
format: uuid
/stages/invitation/{stage_uuid}/:
/stages/invitation/stages/:
get:
operationId: stages_invitation_read
operationId: stages_invitation_stages_list
description: InvitationStage Viewset
parameters:
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: A page number within the paginated result set.
required: false
type: integer
- name: page_size
in: query
description: Number of results to return per page.
required: false
type: integer
responses:
'200':
description: ''
schema:
required:
- count
- results
type: object
properties:
count:
type: integer
next:
type: string
format: uri
x-nullable: true
previous:
type: string
format: uri
x-nullable: true
results:
type: array
items:
$ref: '#/definitions/InvitationStage'
tags:
- stages
post:
operationId: stages_invitation_stages_create
description: InvitationStage Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/InvitationStage'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/InvitationStage'
tags:
- stages
parameters: []
/stages/invitation/stages/{stage_uuid}/:
get:
operationId: stages_invitation_stages_read
description: InvitationStage Viewset
parameters: []
responses:
@ -7037,7 +7047,7 @@ paths:
tags:
- stages
put:
operationId: stages_invitation_update
operationId: stages_invitation_stages_update
description: InvitationStage Viewset
parameters:
- name: data
@ -7053,7 +7063,7 @@ paths:
tags:
- stages
patch:
operationId: stages_invitation_partial_update
operationId: stages_invitation_stages_partial_update
description: InvitationStage Viewset
parameters:
- name: data
@ -7069,7 +7079,7 @@ paths:
tags:
- stages
delete:
operationId: stages_invitation_delete
operationId: stages_invitation_stages_delete
description: InvitationStage Viewset
parameters: []
responses:
@ -7216,6 +7226,26 @@ paths:
operationId: stages_prompt_prompts_list
description: Prompt Viewset
parameters:
- name: field_key
in: query
description: ''
required: false
type: string
- name: label
in: query
description: ''
required: false
type: string
- name: type
in: query
description: ''
required: false
type: string
- name: placeholder
in: query
description: ''
required: false
type: string
- name: ordering
in: query
description: Which field to use when ordering the results.
@ -11335,6 +11365,263 @@ definitions:
type: string
format: uuid
x-nullable: true
Invitation:
description: Invitation Serializer
type: object
properties:
pk:
title: Invite uuid
type: string
format: uuid
readOnly: true
expires:
title: Expires
type: string
format: date-time
x-nullable: true
fixed_data:
title: Fixed data
description: Optional fixed data to enforce on user enrollment.
type: object
created_by:
description: Custom User model to allow easier adding o f user-based settings
required:
- password
- username
- name
type: object
properties:
id:
title: ID
type: integer
readOnly: true
password:
title: Password
type: string
maxLength: 128
minLength: 1
last_login:
title: Last login
type: string
format: date-time
x-nullable: true
username:
title: Username
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
only.
type: string
pattern: ^[\w.@+-]+$
maxLength: 150
minLength: 1
first_name:
title: First name
type: string
maxLength: 150
last_name:
title: Last name
type: string
maxLength: 150
email:
title: Email address
type: string
format: email
maxLength: 254
is_active:
title: Active
description: Designates whether this user should be treated as active.
Unselect this instead of deleting accounts.
type: boolean
date_joined:
title: Date joined
type: string
format: date-time
uuid:
title: Uuid
type: string
format: uuid
readOnly: true
name:
title: Name
description: User's display name.
type: string
minLength: 1
password_change_date:
title: Password change date
type: string
format: date-time
readOnly: true
attributes:
title: Attributes
type: object
groups:
description: ''
type: array
items:
description: Groups are a generic way of categorizing users to apply
permissions, or some other label, to those users. A user can belong
to any number of groups. A user in a group automatically has all the
permissions granted to that group. For example, if the group 'Site
editors' has the permission can_edit_home_page, any user in that group
will have that permission. Beyond permissions, groups are a convenient
way to categorize users to apply some label, or extended functionality,
to them. For example, you could create a group 'Special users', and
you could write code that would do special things to those users --
such as giving them access to a members-only portion of your site,
or sending them members-only email messages.
required:
- name
type: object
properties:
id:
title: ID
type: integer
readOnly: true
name:
title: Name
type: string
maxLength: 150
minLength: 1
permissions:
type: array
items:
type: integer
uniqueItems: true
readOnly: true
user_permissions:
description: ''
type: array
items:
description: "The permissions system provides a way to assign permissions\
\ to specific users and groups of users. The permission system is\
\ used by the Django admin site, but may also be useful in your own\
\ code. The Django admin site uses permissions as follows: - The \"\
add\" permission limits the user's ability to view the \"add\" form\
\ and add an object. - The \"change\" permission limits a user's ability\
\ to view the change list, view the \"change\" form and change an\
\ object. - The \"delete\" permission limits the ability to delete\
\ an object. - The \"view\" permission limits the ability to view\
\ an object. Permissions are set globally per type of object, not\
\ per specific object instance. It is possible to say \"Mary may change\
\ news stories,\" but it's not currently possible to say \"Mary may\
\ change news stories, but only the ones she created herself\" or\
\ \"Mary may only change news stories that have a certain status or\
\ publication date.\" The permissions listed above are automatically\
\ created for each model."
required:
- name
- codename
- content_type
type: object
properties:
id:
title: ID
type: integer
readOnly: true
name:
title: Name
type: string
maxLength: 255
minLength: 1
codename:
title: Codename
type: string
maxLength: 100
minLength: 1
content_type:
title: Content type
type: integer
readOnly: true
sources:
description: ''
type: array
items:
description: Base Authentication source, i.e. an OAuth Provider, SAML
Remote or LDAP Server
required:
- name
- slug
type: object
properties:
pbm_uuid:
title: Pbm uuid
type: string
format: uuid
readOnly: true
name:
title: Name
description: Source's display Name.
type: string
minLength: 1
slug:
title: Slug
description: Internal source name, used in URLs.
type: string
format: slug
pattern: ^[-a-zA-Z0-9_]+$
maxLength: 50
minLength: 1
enabled:
title: Enabled
type: boolean
authentication_flow:
title: Authentication flow
description: Flow to use when authenticating existing users.
type: string
format: uuid
x-nullable: true
enrollment_flow:
title: Enrollment flow
description: Flow to use when enrolling new users.
type: string
format: uuid
x-nullable: true
policies:
type: array
items:
type: string
format: uuid
readOnly: true
uniqueItems: true
property_mappings:
type: array
items:
type: string
format: uuid
uniqueItems: true
readOnly: true
ak_groups:
description: ''
type: array
items:
description: Custom Group model which supports a basic hierarchy
required:
- name
- parent
type: object
properties:
group_uuid:
title: Group uuid
type: string
format: uuid
readOnly: true
name:
title: Name
type: string
maxLength: 80
minLength: 1
is_superuser:
title: Is superuser
description: Users added to this group will be superusers.
type: boolean
attributes:
title: Attributes
type: object
parent:
title: Parent
type: string
format: uuid
readOnly: true
readOnly: true
InvitationStage:
description: InvitationStage Serializer
required:
@ -11373,24 +11660,6 @@ definitions:
no Invitation is given. By default this Stage will cancel the Flow when
no invitation is given.
type: boolean
Invitation:
description: Invitation Serializer
type: object
properties:
pk:
title: Invite uuid
type: string
format: uuid
readOnly: true
expires:
title: Expires
type: string
format: date-time
x-nullable: true
fixed_data:
title: Fixed data
description: Optional fixed data to enforce on user enrollment.
type: object
PasswordStage:
description: PasswordStage Serializer
required:
@ -11496,6 +11765,11 @@ definitions:
type: integer
maximum: 2147483647
minimum: -2147483648
promptstage_set:
description: ''
type: array
items:
$ref: '#/definitions/Stage'
PromptStage:
description: PromptStage Serializer
required:

View File

@ -0,0 +1,27 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { EventContext } from "./Events";
import { User } from "./Users";
export class Invitation {
pk: string;
expires: number;
fixed_date: EventContext;
created_by: User;
constructor() {
throw Error();
}
static get(pk: string): Promise<Invitation> {
return DefaultClient.fetch<Invitation>(["stages", "invitation", "invitations", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Invitation>> {
return DefaultClient.fetch<AKResponse<Invitation>>(["stages", "invitation", "invitations"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages/invitations/${rest}`;
}
}

30
web/src/api/Prompts.ts Normal file
View File

@ -0,0 +1,30 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { Stage } from "./Flows";
export class Prompt {
pk: string;
field_key: string;
label: string;
type: string;
required: boolean;
placeholder: string;
order: number;
promptstage_set: Stage[];
constructor() {
throw Error();
}
static get(pk: string): Promise<Prompt> {
return DefaultClient.fetch<Prompt>(["stages", "prompt", "prompts", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Prompt>> {
return DefaultClient.fetch<AKResponse<Prompt>>(["stages", "prompt", "prompts"], filter);
}
static adminUrl(rest: string): string {
return `/administration/stages/prompts/${rest}`;
}
}

View File

@ -29,7 +29,7 @@ export class SidebarItem {
this.condition = async () => true;
this.activeMatchers = [];
if (this.path) {
this.activeMatchers.push(new RegExp(`^${this.path}`));
this.activeMatchers.push(new RegExp(`^${this.path}$`));
}
}

View File

@ -41,8 +41,8 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
new SidebarItem("Flows").children(
new SidebarItem("Flows", "/flow/flows").activeWhen(`^/flow/flows/(?<slug>${SLUG_REGEX})$`),
new SidebarItem("Stages", "/flow/stages"),
new SidebarItem("Prompts", "/administration/stages_prompts/"),
new SidebarItem("Invitations", "/administration/stages/invitations/"),
new SidebarItem("Prompts", "/flow/stages/prompts"),
new SidebarItem("Invitations", "/flow/stages/invitations"),
).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser);
}),

View File

@ -0,0 +1,72 @@
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/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { Invitation } from "../../api/Invitations";
@customElement("ak-stage-invitation-list")
export class InvitationListPage extends TablePage<Invitation> {
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return gettext("Invitations");
}
pageDescription(): string {
return gettext("Create Invitation Links to enroll Users, and optionally force specific attributes of their account.");
}
pageIcon(): string {
return gettext("pf-icon pf-icon-migration");
}
@property()
order = "expires";
apiEndpoint(page: number): Promise<AKResponse<Invitation>> {
return Invitation.list({
ordering: this.order,
page: page,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [
new TableColumn("ID", "pk"),
new TableColumn("Created by", "created_by"),
new TableColumn("Expiry"),
new TableColumn(""),
];
}
row(item: Invitation): TemplateResult[] {
return [
html`${item.pk}`,
html`${item.created_by.username}`,
html`${new Date(item.expires * 1000).toLocaleString()}`,
html`
<ak-modal-button href="${Invitation.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>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Invitation.adminUrl("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
${super.renderToolbar()}
`;
}
}

View File

@ -0,0 +1,84 @@
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/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { Prompt } from "../../api/Prompts";
@customElement("ak-stage-prompt-list")
export class PromptListPage extends TablePage<Prompt> {
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return gettext("Prompts");
}
pageDescription(): string {
return gettext("Single Prompts that can be used for Prompt Stages.");
}
pageIcon(): string {
return gettext("pf-icon pf-icon-plugged");
}
@property()
order = "order";
apiEndpoint(page: number): Promise<AKResponse<Prompt>> {
return Prompt.list({
ordering: this.order,
page: page,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [
new TableColumn("Field", "field_key"),
new TableColumn("Label", "label"),
new TableColumn("Type", "type"),
new TableColumn("Order", "order"),
new TableColumn("Stages"),
new TableColumn(""),
];
}
row(item: Prompt): TemplateResult[] {
return [
html`${item.field_key}`,
html`${item.label}`,
html`${item.type}`,
html`${item.order}`,
html`${item.promptstage_set.map((stage) => {
return html`<li>${stage.name}</li>`;
})}`,
html`
<ak-modal-button href="${Prompt.adminUrl(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="${Prompt.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>`,
];
}
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${Prompt.adminUrl("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
${gettext("Create")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
${super.renderToolbar()}
`;
}
}

View File

@ -22,6 +22,8 @@ import "./pages/providers/ProviderViewPage";
import "./pages/sources/SourcesListPage";
import "./pages/sources/SourceViewPage";
import "./pages/stages/StageListPage";
import "./pages/stages/InvitationListPage";
import "./pages/stages/PromptListPage";
import "./pages/system-tasks/SystemTaskListPage";
import "./pages/tokens/TokenListPage";
import "./pages/users/UserListPage";
@ -49,6 +51,8 @@ export const ROUTES: Route[] = [
new Route(new RegExp("^/identity/groups$"), html`<ak-group-list></ak-group-list>`),
new Route(new RegExp("^/identity/users$"), html`<ak-user-list></ak-user-list>`),
new Route(new RegExp("^/core/tokens$"), html`<ak-token-list></ak-token-list>`),
new Route(new RegExp("^/flow/stages/invitations$"), html`<ak-stage-invitation-list></ak-stage-invitation-list>`),
new Route(new RegExp("^/flow/stages/prompts$"), html`<ak-stage-prompt-list></ak-stage-prompt-list>`),
new Route(new RegExp("^/flow/stages$"), html`<ak-stage-list></ak-stage-list>`),
new Route(new RegExp("^/flow/flows$"), html`<ak-flow-list></ak-flow-list>`),
new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => {