web: migrate Policy list to web

This commit is contained in:
Jens Langhammer 2021-02-19 17:05:02 +01:00
parent 79089d8981
commit 38bd05867d
7 changed files with 139 additions and 190 deletions

View File

@ -1,151 +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-infrastructure"></i>
{% trans 'Policies' %}
</h1>
<p>{% trans "Allow users to use Applications based on properties, enforce Password Criteria and selectively apply 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-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
<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 'Type' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for policy in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ policy.name }}</div>
{% if not policy.bindings.exists and not policy.promptstage_set.exists %}
<i class="pf-icon pf-icon-warning-triangle"></i>
<small>{% trans 'Warning: Policy is not assigned.' %}</small>
{% else %}
<i class="pf-icon pf-icon-ok"></i>
<small>{% blocktrans with object_count=policy.bindings.all|length %}Assigned to {{ object_count }} objects.{% endblocktrans %}</small>
{% endif %}
</div>
</th>
<td role="cell">
<span>
{{ policy|verbose_name }}
</span>
</td>
<td>
<ak-modal-button href="{% url 'authentik_admin:policy-update' pk=policy.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:policy-test' pk=policy.pk %}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
{% trans 'Test' %}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="{% url 'authentik_admin:policy-delete' pk=policy.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-infrastructure pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">
{% trans 'No Policies.' %}
</h1>
<div class="pf-c-empty-state__body">
{% if request.GET.search != "" %}
{% trans "Your search query doesn't match any policies." %}
{% else %}
{% trans 'Currently no policies exist. Click the button below to create one.' %}
{% endif %}
</div>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<ak-modal-button href="{% url 'authentik_admin:policy-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
{% endfor %}
</ul>
</ak-dropdown>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -73,7 +73,6 @@ urlpatterns = [
name="source-delete", name="source-delete",
), ),
# Policies # Policies
path("policies/", policies.PolicyListView.as_view(), name="policies"),
path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"), path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"),
path( path(
"policies/<uuid:pk>/update/", "policies/<uuid:pk>/update/",

View File

@ -7,42 +7,22 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponse from django.http import HttpResponse
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 FormView from django.views.generic import FormView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from authentik.admin.forms.policies import PolicyTestForm from authentik.admin.forms.policies import PolicyTestForm
from authentik.admin.views.utils import ( from authentik.admin.views.utils import (
BackSuccessUrlMixin, BackSuccessUrlMixin,
DeleteMessageView, DeleteMessageView,
InheritanceCreateView, InheritanceCreateView,
InheritanceListView,
InheritanceUpdateView, InheritanceUpdateView,
SearchListMixin,
UserPaginateListMixin,
) )
from authentik.policies.models import Policy, PolicyBinding from authentik.policies.models import Policy, PolicyBinding
from authentik.policies.process import PolicyProcess, PolicyRequest from authentik.policies.process import PolicyProcess, PolicyRequest
class PolicyListView(
LoginRequiredMixin,
PermissionListMixin,
UserPaginateListMixin,
SearchListMixin,
InheritanceListView,
):
"""Show list of all policies"""
model = Policy
permission_required = "authentik_policies.view_policy"
ordering = "name"
template_name = "administration/policy/list.html"
search_fields = ["name"]
class PolicyCreateView( class PolicyCreateView(
SuccessMessageMixin, SuccessMessageMixin,
BackSuccessUrlMixin, BackSuccessUrlMixin,
@ -56,7 +36,7 @@ class PolicyCreateView(
permission_required = "authentik_policies.add_policy" permission_required = "authentik_policies.add_policy"
template_name = "generic/create.html" template_name = "generic/create.html"
success_url = reverse_lazy("authentik_admin:policies") success_url = "/"
success_message = _("Successfully created Policy") success_message = _("Successfully created Policy")
@ -73,7 +53,7 @@ class PolicyUpdateView(
permission_required = "authentik_policies.change_policy" permission_required = "authentik_policies.change_policy"
template_name = "generic/update.html" template_name = "generic/update.html"
success_url = reverse_lazy("authentik_admin:policies") success_url = "/"
success_message = _("Successfully updated Policy") success_message = _("Successfully updated Policy")
@ -84,7 +64,7 @@ class PolicyDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
permission_required = "authentik_policies.delete_policy" permission_required = "authentik_policies.delete_policy"
template_name = "generic/delete.html" template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:policies") success_url = "/"
success_message = _("Successfully deleted Policy") success_message = _("Successfully deleted Policy")

View File

@ -1,15 +1,18 @@
import { DefaultClient, BaseInheritanceModel, AKResponse, QueryArguments } from "./Client"; import { DefaultClient, BaseInheritanceModel, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class Policy implements BaseInheritanceModel { export class Policy implements BaseInheritanceModel {
pk: string; pk: string;
name: string; name: string;
execution_logging: boolean;
object_type: string;
verbose_name: string;
verbose_name_plural: string;
bound_to: number;
constructor() { constructor() {
throw Error(); throw Error();
} }
object_type: string;
verbose_name: string;
verbose_name_plural: string;
static get(pk: string): Promise<Policy> { static get(pk: string): Promise<Policy> {
return DefaultClient.fetch<Policy>(["policies", "all", pk]); return DefaultClient.fetch<Policy>(["policies", "all", pk]);
@ -24,4 +27,12 @@ export class Policy implements BaseInheritanceModel {
return r.count; return r.count;
}); });
} }
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["policies", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/policies/${rest}`;
}
} }

View File

@ -33,7 +33,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);
}), }),
new SidebarItem("Customisation").children( new SidebarItem("Customisation").children(
new SidebarItem("Policies", "/administration/policies/"), new SidebarItem("Policies", "/policies"),
new SidebarItem("Property Mappings", "/property-mappings"), new SidebarItem("Property Mappings", "/property-mappings"),
).when((): Promise<boolean> => { ).when((): Promise<boolean> => {
return User.me().then(u => u.is_superuser); return User.me().then(u => u.is_superuser);

View File

@ -0,0 +1,108 @@
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 "../../elements/buttons/SpinnerButton";
import { TableColumn } from "../../elements/table/Table";
import { Policy } from "../../api/Policies";
import { until } from "lit-html/directives/until";
@customElement("ak-policy-list")
export class PolicyListPage extends TablePage<Policy> {
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return gettext("Policies");
}
pageDescription(): string {
return gettext("Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages.");
}
pageIcon(): string {
return gettext("pf-icon pf-icon-infrastructure");
}
@property()
order = "name";
apiEndpoint(page: number): Promise<AKResponse<Policy>> {
return Policy.list({
ordering: this.order,
page: page,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [
new TableColumn("Name", "name"),
new TableColumn("Type"),
new TableColumn(""),
];
}
row(item: Policy): TemplateResult[] {
return [
html`<div>
<div>${item.name}</div>
${item.bound_to > 0 ?
html`<i class="pf-icon pf-icon-ok"></i>
<small>
${gettext(`Assigned to ${item.bound_to} objects.`)}
</small>`:
html`<i class="pf-icon pf-icon-warning-triangle"></i>
<small>${gettext("Warning: Policy is not assigned.")}/small>`}
</div>`,
html`${item.verbose_name}`,
html`
<ak-modal-button href="${Policy.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="${Policy.adminUrl(`${item.pk}/test/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Test")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="${Policy.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-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${gettext("Create")}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(Policy.getTypes().then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">
<button slot="trigger" class="pf-c-dropdown__menu-item">${type.name}<br>
<small>${type.description}</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
}
}

View File

@ -1,23 +1,24 @@
import { html } from "lit-html"; import { html } from "lit-html";
import { Route, SLUG_REGEX, ID_REGEX, UUID_REGEX } from "./elements/router/Route"; import { Route, SLUG_REGEX, ID_REGEX, UUID_REGEX } from "./elements/router/Route";
import "./pages/LibraryPage";
import "./pages/admin-overview/AdminOverviewPage"; import "./pages/admin-overview/AdminOverviewPage";
import "./pages/applications/ApplicationListPage"; import "./pages/applications/ApplicationListPage";
import "./pages/applications/ApplicationViewPage"; import "./pages/applications/ApplicationViewPage";
import "./pages/sources/SourcesListPage"; import "./pages/crypto/CertificateKeyPairListPage";
import "./pages/sources/SourceViewPage"; import "./pages/events/EventInfoPage";
import "./pages/events/EventListPage";
import "./pages/events/RuleListPage";
import "./pages/events/TransportListPage";
import "./pages/flows/FlowListPage"; import "./pages/flows/FlowListPage";
import "./pages/flows/FlowViewPage"; import "./pages/flows/FlowViewPage";
import "./pages/events/EventListPage"; import "./pages/LibraryPage";
import "./pages/events/EventInfoPage"; import "./pages/outposts/OutpostListPage";
import "./pages/events/TransportListPage"; import "./pages/policies/PolicyListPage";
import "./pages/events/RuleListPage"; import "./pages/property-mappings/PropertyMappingListPage";
import "./pages/providers/ProviderListPage"; import "./pages/providers/ProviderListPage";
import "./pages/providers/ProviderViewPage"; import "./pages/providers/ProviderViewPage";
import "./pages/property-mappings/PropertyMappingListPage"; import "./pages/sources/SourcesListPage";
import "./pages/outposts/OutpostListPage"; import "./pages/sources/SourceViewPage";
import "./pages/crypto/CertificateKeyPairListPage";
export const ROUTES: Route[] = [ export const ROUTES: Route[] = [
// Prevent infinite Shell loops // Prevent infinite Shell loops
@ -37,6 +38,7 @@ export const ROUTES: Route[] = [
new Route(new RegExp(`^/sources/(?<slug>${SLUG_REGEX})$`)).then((args) => { new Route(new RegExp(`^/sources/(?<slug>${SLUG_REGEX})$`)).then((args) => {
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("^/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>`;