web: migrate Group list to web

This commit is contained in:
Jens Langhammer 2021-02-19 17:18:09 +01:00
parent 71f771c22c
commit 029c6cd182
7 changed files with 103 additions and 145 deletions

View File

@ -1,114 +0,0 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-users"></i>
{% trans 'Groups' %}
</h1>
<p>{% trans "Group users together and give them permissions based on the membership." %}
</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:group-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 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Parent' %}</th>
<th role="columnheader" scope="col">{% trans 'Members' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for group in object_list %}
<tr role="row">
<td role="cell">
<span>
{{ group.name }}
</span>
</td>
<td role="cell">
<span>
{{ group.parent }}
</span>
</td>
<td role="cell">
<span>
{{ group.users.all|length }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:group-update' pk=group.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Edit' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:group-delete' pk=group.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-users pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Groups.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any groups." %}
{% else %}
{% trans 'Currently no group exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-modal-button href="{% url 'authentik_admin:group-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

@ -269,7 +269,6 @@ urlpatterns = [
name="user-password-reset", name="user-password-reset",
), ),
# Groups # Groups
path("groups/", groups.GroupListView.as_view(), name="groups"),
path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"), path("groups/create/", groups.GroupCreateView.as_view(), name="group-create"),
path( path(
"groups/<uuid:pk>/update/", "groups/<uuid:pk>/update/",

View File

@ -4,38 +4,16 @@ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin, PermissionRequiredMixin as DjangoPermissionRequiredMixin,
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import ( from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
BackSuccessUrlMixin,
DeleteMessageView,
SearchListMixin,
UserPaginateListMixin,
)
from authentik.core.forms.groups import GroupForm from authentik.core.forms.groups import GroupForm
from authentik.core.models import Group from authentik.core.models import Group
from authentik.lib.views import CreateAssignPermView from authentik.lib.views import CreateAssignPermView
class GroupListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
ListView,
):
"""Show list of all groups"""
model = Group
permission_required = "authentik_core.view_group"
ordering = "name"
template_name = "administration/group/list.html"
search_fields = ["name", "attributes"]
class GroupCreateView( class GroupCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin, BackSuccessUrlMixin,
@ -50,7 +28,7 @@ class GroupCreateView(
permission_required = "authentik_core.add_group" permission_required = "authentik_core.add_group"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:groups") success_url = "/"
success_message = _("Successfully created Group") success_message = _("Successfully created Group")
@ -68,7 +46,7 @@ class GroupUpdateView(
permission_required = "authentik_core.change_group" permission_required = "authentik_core.change_group"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:groups") success_url = "/"
success_message = _("Successfully updated Group") success_message = _("Successfully updated Group")
@ -79,5 +57,5 @@ class GroupDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
permission_required = "authentik_flows.delete_group" permission_required = "authentik_flows.delete_group"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:groups") success_url = "/"
success_message = _("Successfully deleted Group") success_message = _("Successfully deleted Group")

View File

@ -1,15 +1,28 @@
import { DefaultClient, QueryArguments, AKResponse } from "./Client";
import { EventContext } from "./Events"; import { EventContext } from "./Events";
export class Group { export class Group {
group_uuid: string; pk: string;
name: string; name: string;
is_superuser: boolean; is_superuser: boolean;
attributes: EventContext; attributes: EventContext;
parent?: Group; parent?: Group;
users: number[];
constructor() { constructor() {
throw Error(); throw Error();
} }
static get(pk: string): Promise<Group> {
return DefaultClient.fetch<Group>(["core", "groups", pk]);
}
static list(filter?: QueryArguments): Promise<AKResponse<Group>> {
return DefaultClient.fetch<AKResponse<Group>>(["core", "groups"], filter);
}
static adminUrl(rest: string): string {
return `/administration/groups/${rest}`;
}
} }

View File

@ -48,7 +48,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
}), }),
new SidebarItem("Identity & Cryptography").children( new SidebarItem("Identity & Cryptography").children(
new SidebarItem("User", "/administration/users/"), new SidebarItem("User", "/administration/users/"),
new SidebarItem("Groups", "/administration/groups/"), new SidebarItem("Groups", "/groups"),
new SidebarItem("Certificates", "/crypto/certificates"), new SidebarItem("Certificates", "/crypto/certificates"),
new SidebarItem("Tokens", "/administration/tokens/"), new SidebarItem("Tokens", "/administration/tokens/"),
).when((): Promise<boolean> => { ).when((): Promise<boolean> => {

View File

@ -0,0 +1,80 @@
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 { Group } from "../../api/Groups";
@customElement("ak-group-list")
export class GroupListPage extends TablePage<Group> {
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return gettext("Groups");
}
pageDescription(): string {
return gettext("Group users together and give them permissions based on the membership.");
}
pageIcon(): string {
return gettext("pf-icon pf-icon-users");
}
@property()
order = "slug";
apiEndpoint(page: number): Promise<AKResponse<Group>> {
return Group.list({
ordering: this.order,
page: page,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [
new TableColumn("Name", "name"),
new TableColumn("Parent", "parent"),
new TableColumn("Members"),
new TableColumn("Superuser privileges?"),
new TableColumn(""),
];
}
row(item: Group): TemplateResult[] {
return [
html`${item.name}`,
html`${item.parent || "-"}`,
html`${item.users.length}`,
html`${item.is_superuser ? "Yes" : "No"}`,
html`
<ak-modal-button href="${Group.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="${Group.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=${Group.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

@ -19,6 +19,7 @@ import "./pages/providers/ProviderListPage";
import "./pages/providers/ProviderViewPage"; import "./pages/providers/ProviderViewPage";
import "./pages/sources/SourcesListPage"; import "./pages/sources/SourcesListPage";
import "./pages/sources/SourceViewPage"; import "./pages/sources/SourceViewPage";
import "./pages/groups/GroupListPage";
export const ROUTES: Route[] = [ export const ROUTES: Route[] = [
// Prevent infinite Shell loops // Prevent infinite Shell loops
@ -39,6 +40,7 @@ export const ROUTES: Route[] = [
return html`<ak-source-view .args=${args}></ak-source-view>`; return html`<ak-source-view .args=${args}></ak-source-view>`;
}), }),
new Route(new RegExp("^/policies$"), html`<ak-policy-list></ak-policy-list>`), new Route(new RegExp("^/policies$"), html`<ak-policy-list></ak-policy-list>`),
new Route(new RegExp("^/groups$"), html`<ak-group-list></ak-group-list>`),
new Route(new RegExp("^/flows$"), html`<ak-flow-list></ak-flow-list>`), new Route(new RegExp("^/flows$"), html`<ak-flow-list></ak-flow-list>`),
new Route(new RegExp(`^/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => { new Route(new RegExp(`^/flows/(?<slug>${SLUG_REGEX})$`)).then((args) => {
return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`; return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;