web: migrate remaining list views to web
This commit is contained in:
parent
9d4c22c706
commit
854d94056e
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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(),
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
454
swagger.yaml
454
swagger.yaml
|
@ -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:
|
||||
|
|
27
web/src/api/Invitations.ts
Normal file
27
web/src/api/Invitations.ts
Normal 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
30
web/src/api/Prompts.ts
Normal 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}`;
|
||||
}
|
||||
}
|
|
@ -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}$`));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}),
|
||||
|
|
72
web/src/pages/stages/InvitationListPage.ts
Normal file
72
web/src/pages/stages/InvitationListPage.ts
Normal 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()}
|
||||
`;
|
||||
}
|
||||
}
|
84
web/src/pages/stages/PromptListPage.ts
Normal file
84
web/src/pages/stages/PromptListPage.ts
Normal 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()}
|
||||
`;
|
||||
}
|
||||
}
|
|
@ -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) => {
|
||||
|
|
Reference in a new issue