web: remove policy bindings page (#370)
* admin: accept ?target for PolicyBindingCreateView * core: fix rendering of hidden fields in horizontal form * web: add create button for application's bound policies * admin: fix delete form not working * web: fix ak-refresh event not being dispatched correctly * web: fix linting errors * admin: fix tests not loading * build(deps-dev): bump eslint from 7.14.0 to 7.15.0 in /web (#372) Bumps [eslint](https://github.com/eslint/eslint) from 7.14.0 to 7.15.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v7.14.0...v7.15.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump rollup from 2.34.1 to 2.34.2 in /web (#373) Bumps [rollup](https://github.com/rollup/rollup) from 2.34.1 to 2.34.2. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.34.1...v2.34.2) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @types/codemirror from 0.0.100 to 0.0.102 in /web (#374) Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.100 to 0.0.102. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump bandit from 1.6.2 to 1.6.3 (#371) * build(deps-dev): bump bandit from 1.6.2 to 1.6.3 Bumps [bandit](https://github.com/PyCQA/bandit) from 1.6.2 to 1.6.3. - [Release notes](https://github.com/PyCQA/bandit/releases) - [Commits](https://github.com/PyCQA/bandit/compare/1.6.2...1.6.3) Signed-off-by: dependabot[bot] <support@github.com> * root: update for new bandit version Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org> * web: add header to bound-policies * web: fix spacing between bulk_select buttons * web: add separate ak-bound-policies-list, add flow view page * web: fix flows' policies not loading * Squashed commit of the following: commite535cb0ec8
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Dec 10 09:58:07 2020 +0100 build(deps): bump boto3 from 1.16.32 to 1.16.33 (#383) Bumps [boto3](https://github.com/boto/boto3) from 1.16.32 to 1.16.33. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.16.32...1.16.33) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit8c1f55d3e3
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Dec 9 09:06:45 2020 +0100 build(deps): bump boto3 from 1.16.31 to 1.16.32 (#382) Bumps [boto3](https://github.com/boto/boto3) from 1.16.31 to 1.16.32. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.16.31...1.16.32) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commitc3a2cb44cd
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Dec 9 09:06:29 2020 +0100 build(deps): bump celery from 5.0.3 to 5.0.4 (#380) Bumps [celery](https://github.com/celery/celery) from 5.0.3 to 5.0.4. - [Release notes](https://github.com/celery/celery/releases) - [Changelog](https://github.com/celery/celery/blob/master/Changelog.rst) - [Commits](https://github.com/celery/celery/compare/v5.0.3...v5.0.4) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit682401bbf2
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Dec 9 07:20:45 2020 +0100 build(deps): bump uvicorn from 0.12.3 to 0.13.0 (#381) Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.12.3 to 0.13.0. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.12.3...0.13.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit3e6e167348
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 10:32:00 2020 +0100 build(deps-dev): bump @typescript-eslint/parser in /web (#377) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.9.0 to 4.9.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.9.1/packages/parser) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commitd08c1b7b02
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 10:31:47 2020 +0100 build(deps): bump @sentry/browser from 5.28.0 to 5.29.0 in /web (#378) Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 5.28.0 to 5.29.0. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/5.28.0...5.29.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit94d70d252c
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 09:02:37 2020 +0100 build(deps): bump boto3 from 1.16.30 to 1.16.31 (#375) Bumps [boto3](https://github.com/boto/boto3) from 1.16.30 to 1.16.31. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.16.30...1.16.31) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commitccfe746dd5
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 09:02:28 2020 +0100 build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#376) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.9.0 to 4.9.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.9.1/packages/eslint-plugin) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commitef5dffa96a
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 09:02:16 2020 +0100 build(deps): bump @sentry/tracing from 5.28.0 to 5.29.0 in /web (#379) Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 5.28.0 to 5.29.0. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/5.28.0...5.29.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit2caa1e7650
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Dec 7 11:21:07 2020 +0100 build(deps-dev): bump bandit from 1.6.2 to 1.6.3 (#371) * build(deps-dev): bump bandit from 1.6.2 to 1.6.3 Bumps [bandit](https://github.com/PyCQA/bandit) from 1.6.2 to 1.6.3. - [Release notes](https://github.com/PyCQA/bandit/releases) - [Commits](https://github.com/PyCQA/bandit/compare/1.6.2...1.6.3) Signed-off-by: dependabot[bot] <support@github.com> * root: update for new bandit version Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jens Langhammer <jens.langhammer@beryju.org> commit2246f3a534
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Dec 7 10:26:01 2020 +0100 build(deps): bump @types/codemirror from 0.0.100 to 0.0.102 in /web (#374) Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.100 to 0.0.102. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit95ba00cb79
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Dec 7 09:09:49 2020 +0100 build(deps): bump rollup from 2.34.1 to 2.34.2 in /web (#373) Bumps [rollup](https://github.com/rollup/rollup) from 2.34.1 to 2.34.2. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.34.1...v2.34.2) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit2ab4d6620f
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Dec 7 09:09:24 2020 +0100 build(deps-dev): bump eslint from 7.14.0 to 7.15.0 in /web (#372) Bumps [eslint](https://github.com/eslint/eslint) from 7.14.0 to 7.15.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v7.14.0...v7.15.0) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * web: fix linting error * web: simplify sidebar logic * web: add support for multiple active matchers per sidebar item * web: move router to elements * flows: add stage_obj to flows api * sources/*: make all sources implement SerializerModel * web: improve listing of stages * web: implement expandable table * web/table: use TemplateResult as return value for row() * web: add empty state, fix link for BoundStageList * admin: make stage binding form accept ?target like policy binding * web: fix styles in dark mode for expanding tables * flows: add policybindingmodel_ptr_id to FlowStageBinding API * web: improve wording for policies * web: fix dark theme for tertiary buttons and static modals * web: implement SourceViewPage * web: add empty state for BoundPoliciesList * web: cleanup URLs for FlowStageBindings * root: remove url attribute from ak-messages Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
e6a776be07
commit
488e8f769a
|
@ -53,10 +53,10 @@
|
|||
{% for flow in object_list %}
|
||||
<tr role="row">
|
||||
<th role="columnheader">
|
||||
<div>
|
||||
<a href="/flows/{{ flow.slug }}/">
|
||||
<div><code>{{ flow.slug }}</code></div>
|
||||
<small>{{ flow.name }}</small>
|
||||
</div>
|
||||
</a>
|
||||
</th>
|
||||
<td role="cell">
|
||||
<span>
|
||||
|
|
|
@ -63,12 +63,12 @@
|
|||
{% for source in object_list %}
|
||||
<tr role="row">
|
||||
<th role="columnheader">
|
||||
<div>
|
||||
<a href="/sources/{{ source.slug }}/">
|
||||
<div>{{ source.name }}</div>
|
||||
{% if not source.enabled %}
|
||||
<small>{% trans 'Disabled' %}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</a>
|
||||
</th>
|
||||
<td role="cell">
|
||||
<span>
|
||||
|
|
0
authentik/admin/tests/__init__.py
Normal file
0
authentik/admin/tests/__init__.py
Normal file
26
authentik/admin/tests/test_policy_binding.py
Normal file
26
authentik/admin/tests/test_policy_binding.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
"""admin tests"""
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from authentik.admin.views.policies_bindings import PolicyBindingCreateView
|
||||
from authentik.core.models import Application
|
||||
|
||||
|
||||
class TestPolicyBindingView(TestCase):
|
||||
"""Generic admin tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_without_get_param(self):
|
||||
"""Test PolicyBindingCreateView without get params"""
|
||||
request = self.factory.get("/")
|
||||
view = PolicyBindingCreateView(request=request)
|
||||
self.assertEqual(view.get_initial(), {})
|
||||
|
||||
def test_with_param(self):
|
||||
"""Test PolicyBindingCreateView with get params"""
|
||||
target = Application.objects.create(name="test")
|
||||
request = self.factory.get("/", {"target": target.pk.hex})
|
||||
view = PolicyBindingCreateView(request=request)
|
||||
self.assertEqual(view.get_initial(), {"target": target, "order": 0})
|
26
authentik/admin/tests/test_stage_bindings.py
Normal file
26
authentik/admin/tests/test_stage_bindings.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
"""admin tests"""
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from authentik.admin.views.stages_bindings import StageBindingCreateView
|
||||
from authentik.flows.models import Flow
|
||||
|
||||
|
||||
class TestStageBindingView(TestCase):
|
||||
"""Generic admin tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_without_get_param(self):
|
||||
"""Test StageBindingCreateView without get params"""
|
||||
request = self.factory.get("/")
|
||||
view = StageBindingCreateView(request=request)
|
||||
self.assertEqual(view.get_initial(), {})
|
||||
|
||||
def test_with_param(self):
|
||||
"""Test StageBindingCreateView with get params"""
|
||||
target = Flow.objects.create(name="test", slug="test")
|
||||
request = self.factory.get("/", {"target": target.pk.hex})
|
||||
view = StageBindingCreateView(request=request)
|
||||
self.assertEqual(view.get_initial(), {"target": target, "order": 0})
|
|
@ -1,10 +1,12 @@
|
|||
"""authentik PolicyBinding administration"""
|
||||
from typing import Any
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.db.models import QuerySet
|
||||
from django.db.models import Max, QuerySet
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import ListView, UpdateView
|
||||
|
@ -18,7 +20,7 @@ from authentik.admin.views.utils import (
|
|||
)
|
||||
from authentik.lib.views import CreateAssignPermView
|
||||
from authentik.policies.forms import PolicyBindingForm
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.policies.models import PolicyBinding, PolicyBindingModel
|
||||
|
||||
|
||||
class PolicyBindingListView(
|
||||
|
@ -67,6 +69,22 @@ class PolicyBindingCreateView(
|
|||
success_url = reverse_lazy("authentik_admin:policies-bindings")
|
||||
success_message = _("Successfully created PolicyBinding")
|
||||
|
||||
def get_initial(self) -> dict[str, Any]:
|
||||
if "target" in self.request.GET:
|
||||
initial_target_pk = self.request.GET["target"]
|
||||
targets = PolicyBindingModel.objects.filter(
|
||||
pk=initial_target_pk
|
||||
).select_subclasses()
|
||||
if not targets.exists():
|
||||
return {}
|
||||
max_order = PolicyBinding.objects.filter(target=targets.first()).aggregate(
|
||||
Max("order")
|
||||
)["order__max"]
|
||||
if not isinstance(max_order, int):
|
||||
max_order = -1
|
||||
return {"target": targets.first(), "order": max_order + 1}
|
||||
return super().get_initial()
|
||||
|
||||
|
||||
class PolicyBindingUpdateView(
|
||||
SuccessMessageMixin,
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
"""authentik StageBinding administration"""
|
||||
from typing import Any
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.db.models import Max
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import ListView, UpdateView
|
||||
|
@ -15,7 +18,7 @@ from authentik.admin.views.utils import (
|
|||
UserPaginateListMixin,
|
||||
)
|
||||
from authentik.flows.forms import FlowStageBindingForm
|
||||
from authentik.flows.models import FlowStageBinding
|
||||
from authentik.flows.models import Flow, FlowStageBinding
|
||||
from authentik.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
|
@ -47,6 +50,20 @@ class StageBindingCreateView(
|
|||
success_url = reverse_lazy("authentik_admin:stage-bindings")
|
||||
success_message = _("Successfully created StageBinding")
|
||||
|
||||
def get_initial(self) -> dict[str, Any]:
|
||||
if "target" in self.request.GET:
|
||||
initial_target_pk = self.request.GET["target"]
|
||||
targets = Flow.objects.filter(pk=initial_target_pk).select_subclasses()
|
||||
if not targets.exists():
|
||||
return {}
|
||||
max_order = FlowStageBinding.objects.filter(
|
||||
target=targets.first()
|
||||
).aggregate(Max("order"))["order__max"]
|
||||
if not isinstance(max_order, int):
|
||||
max_order = -1
|
||||
return {"target": targets.first(), "order": max_order + 1}
|
||||
return super().get_initial()
|
||||
|
||||
|
||||
class StageBindingUpdateView(
|
||||
SuccessMessageMixin,
|
||||
|
|
|
@ -15,6 +15,12 @@ class SourceSerializer(ModelSerializer):
|
|||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace("source", "")
|
||||
|
||||
def to_representation(self, instance: Source):
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if instance.__class__ == Source:
|
||||
return super().to_representation(instance)
|
||||
return instance.serializer(instance=instance).data
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Source
|
||||
|
@ -26,6 +32,7 @@ class SourceViewSet(ReadOnlyModelViewSet):
|
|||
|
||||
queryset = Source.objects.all()
|
||||
serializer_class = SourceSerializer
|
||||
lookup_field = "slug"
|
||||
|
||||
def get_queryset(self):
|
||||
return Source.objects.select_subclasses()
|
||||
|
|
|
@ -20,7 +20,7 @@ from authentik.core.exceptions import PropertyMappingExpressionException
|
|||
from authentik.core.signals import password_changed
|
||||
from authentik.core.types import UILoginButton
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.lib.models import CreatedUpdatedModel
|
||||
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
|
||||
from authentik.policies.models import PolicyBindingModel
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
@ -200,7 +200,7 @@ class Application(PolicyBindingModel):
|
|||
verbose_name_plural = _("Applications")
|
||||
|
||||
|
||||
class Source(PolicyBindingModel):
|
||||
class Source(SerializerModel, PolicyBindingModel):
|
||||
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
|
||||
|
||||
name = models.TextField(help_text=_("Source's display Name."))
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<div class="pf-l-stack__item">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<form action="" method="post" class="pf-c-form">
|
||||
<form id="delete-form" action="" method="post" class="pf-c-form">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{% blocktrans with object_type=object|verbose_name name=object %}
|
||||
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
</section>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<input class="pf-c-button pf-m-danger" type="submit" value="{% trans 'Delete' %}" />
|
||||
<input class="pf-c-button pf-m-danger" type="submit" form="delete-form" value="{% trans 'Delete' %}" />
|
||||
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
<ak-messages url="{% url 'authentik_api:messages-list' %}"></ak-messages>
|
||||
<ak-messages></ak-messages>
|
||||
|
||||
<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</filter>
|
||||
</svg>
|
||||
</div>
|
||||
<ak-messages url="{% url 'authentik_api:messages-list' %}"></ak-messages>
|
||||
<ak-messages></ak-messages>
|
||||
<div class="pf-c-login">
|
||||
<div class="pf-c-login__container">
|
||||
<header class="pf-c-login__header">
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if field.field.widget|fieldtype == 'HiddenInput' %}
|
||||
{{ field }}
|
||||
{% else %}
|
||||
<div class="pf-c-form__group {% if field.errors %} has-error {% endif %}">
|
||||
{% if field.field.widget|fieldtype == 'RadioSelect' %}
|
||||
<div class="pf-c-form__group-label">
|
||||
|
@ -105,4 +108,5 @@
|
|||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -21,6 +21,7 @@ class FlowSerializer(ModelSerializer):
|
|||
model = Flow
|
||||
fields = [
|
||||
"pk",
|
||||
"policybindingmodel_ptr_id",
|
||||
"name",
|
||||
"slug",
|
||||
"title",
|
||||
|
@ -37,31 +38,7 @@ class FlowViewSet(ModelViewSet):
|
|||
|
||||
queryset = Flow.objects.all()
|
||||
serializer_class = FlowSerializer
|
||||
|
||||
|
||||
class FlowStageBindingSerializer(ModelSerializer):
|
||||
"""FlowStageBinding Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = FlowStageBinding
|
||||
fields = [
|
||||
"pk",
|
||||
"target",
|
||||
"stage",
|
||||
"evaluate_on_plan",
|
||||
"re_evaluate_policies",
|
||||
"order",
|
||||
"policies",
|
||||
]
|
||||
|
||||
|
||||
class FlowStageBindingViewSet(ModelViewSet):
|
||||
"""FlowStageBinding Viewset"""
|
||||
|
||||
queryset = FlowStageBinding.objects.all()
|
||||
serializer_class = FlowStageBindingSerializer
|
||||
filterset_fields = "__all__"
|
||||
lookup_field = "slug"
|
||||
|
||||
|
||||
class StageSerializer(ModelSerializer):
|
||||
|
@ -92,3 +69,32 @@ class StageViewSet(ReadOnlyModelViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
return Stage.objects.select_subclasses()
|
||||
|
||||
|
||||
class FlowStageBindingSerializer(ModelSerializer):
|
||||
"""FlowStageBinding Serializer"""
|
||||
|
||||
stage_obj = StageSerializer(read_only=True, source="stage")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = FlowStageBinding
|
||||
fields = [
|
||||
"pk",
|
||||
"policybindingmodel_ptr_id",
|
||||
"target",
|
||||
"stage",
|
||||
"stage_obj",
|
||||
"evaluate_on_plan",
|
||||
"re_evaluate_policies",
|
||||
"order",
|
||||
"policies",
|
||||
]
|
||||
|
||||
|
||||
class FlowStageBindingViewSet(ModelViewSet):
|
||||
"""FlowStageBinding Viewset"""
|
||||
|
||||
queryset = FlowStageBinding.objects.all()
|
||||
serializer_class = FlowStageBindingSerializer
|
||||
filterset_fields = "__all__"
|
||||
|
|
|
@ -37,6 +37,11 @@ class FlowStageBindingForm(forms.ModelForm):
|
|||
queryset=Stage.objects.all().select_subclasses(), to_field_name="stage_uuid"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if "target" in self.initial:
|
||||
self.fields["target"].widget = forms.HiddenInput()
|
||||
|
||||
class Meta:
|
||||
|
||||
model = FlowStageBinding
|
||||
|
|
|
@ -20,6 +20,11 @@ class PolicyBindingForm(forms.ModelForm):
|
|||
queryset=Policy.objects.all().select_subclasses(),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if "target" in self.initial:
|
||||
self.fields["target"].widget = forms.HiddenInput()
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PolicyBinding
|
||||
|
|
|
@ -18,6 +18,8 @@ class ChannelsStorage(FallbackStorage):
|
|||
def _store(self, messages: list[Message], response, *args, **kwargs):
|
||||
prefix = f"user_{self.request.user.pk}_messages_"
|
||||
keys = cache.keys(f"{prefix}*")
|
||||
if len(keys) < 1:
|
||||
return super()._store(messages, response, *args, **kwargs)
|
||||
for key in keys:
|
||||
uid = key.replace(prefix, "")
|
||||
for message in messages:
|
||||
|
@ -30,4 +32,4 @@ class ChannelsStorage(FallbackStorage):
|
|||
"message": message.message,
|
||||
},
|
||||
)
|
||||
return super()._store(messages, response, *args, **kwargs)
|
||||
return None
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.db import models
|
|||
from django.forms import ModelForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from ldap3 import ALL, Connection, Server
|
||||
from rest_framework.serializers import Serializer
|
||||
|
||||
from authentik.core.models import Group, PropertyMapping, Source
|
||||
from authentik.lib.models import DomainlessURLValidator
|
||||
|
@ -73,6 +74,12 @@ class LDAPSource(Source):
|
|||
|
||||
return LDAPSourceForm
|
||||
|
||||
@property
|
||||
def serializer(self) -> Type[Serializer]:
|
||||
from authentik.sources.ldap.api import LDAPSourceSerializer
|
||||
|
||||
return LDAPSourceSerializer
|
||||
|
||||
def state_cache_prefix(self, suffix: str) -> str:
|
||||
"""Key by which the ldap source status is saved"""
|
||||
return f"source_ldap_{self.pk}_state_{suffix}"
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import models
|
|||
from django.forms import ModelForm
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.serializers import Serializer
|
||||
|
||||
from authentik.core.models import Source, UserSourceConnection
|
||||
from authentik.core.types import UILoginButton
|
||||
|
@ -46,6 +47,12 @@ class OAuthSource(Source):
|
|||
|
||||
return OAuthSourceForm
|
||||
|
||||
@property
|
||||
def serializer(self) -> Type[Serializer]:
|
||||
from authentik.sources.oauth.api import OAuthSourceSerializer
|
||||
|
||||
return OAuthSourceSerializer
|
||||
|
||||
@property
|
||||
def ui_login_button(self) -> UILoginButton:
|
||||
return UILoginButton(
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.http import HttpRequest
|
|||
from django.shortcuts import reverse
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.serializers import Serializer
|
||||
|
||||
from authentik.core.models import Source
|
||||
from authentik.core.types import UILoginButton
|
||||
|
@ -143,6 +144,12 @@ class SAMLSource(Source):
|
|||
|
||||
return SAMLSourceForm
|
||||
|
||||
@property
|
||||
def serializer(self) -> Type[Serializer]:
|
||||
from authentik.sources.saml.api import SAMLSourceSerializer
|
||||
|
||||
return SAMLSourceSerializer
|
||||
|
||||
def get_issuer(self, request: HttpRequest) -> str:
|
||||
"""Get Source's Issuer, falling back to our Metadata URL if none is set"""
|
||||
if self.issuer is None:
|
||||
|
|
75
swagger.yaml
75
swagger.yaml
|
@ -1129,7 +1129,7 @@ paths:
|
|||
tags:
|
||||
- flows
|
||||
parameters: []
|
||||
/flows/instances/{flow_uuid}/:
|
||||
/flows/instances/{slug}/:
|
||||
get:
|
||||
operationId: flows_instances_read
|
||||
description: Flow Viewset
|
||||
|
@ -1183,12 +1183,13 @@ paths:
|
|||
tags:
|
||||
- flows
|
||||
parameters:
|
||||
- name: flow_uuid
|
||||
- name: slug
|
||||
in: path
|
||||
description: A UUID string identifying this Flow.
|
||||
description: Visible in the URL.
|
||||
required: true
|
||||
type: string
|
||||
format: uuid
|
||||
format: slug
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
/outposts/outposts/:
|
||||
get:
|
||||
operationId: outposts_outposts_list
|
||||
|
@ -3788,7 +3789,7 @@ paths:
|
|||
tags:
|
||||
- sources
|
||||
parameters: []
|
||||
/sources/all/{pbm_uuid}/:
|
||||
/sources/all/{slug}/:
|
||||
get:
|
||||
operationId: sources_all_read
|
||||
description: Source Viewset
|
||||
|
@ -3801,12 +3802,13 @@ paths:
|
|||
tags:
|
||||
- sources
|
||||
parameters:
|
||||
- name: pbm_uuid
|
||||
- name: slug
|
||||
in: path
|
||||
description: A UUID string identifying this source.
|
||||
description: Internal source name, used in URLs.
|
||||
required: true
|
||||
type: string
|
||||
format: uuid
|
||||
format: slug
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
/sources/ldap/:
|
||||
get:
|
||||
operationId: sources_ldap_list
|
||||
|
@ -6749,6 +6751,30 @@ definitions:
|
|||
description: Optional Private Key. If this is set, you can use this keypair
|
||||
for encryption.
|
||||
type: string
|
||||
Stage:
|
||||
title: Stage obj
|
||||
description: Stage Serializer
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
title: Stage uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
minLength: 1
|
||||
__type__:
|
||||
title: 'type '
|
||||
type: string
|
||||
readOnly: true
|
||||
verbose_name:
|
||||
title: Verbose name
|
||||
type: string
|
||||
readOnly: true
|
||||
FlowStageBinding:
|
||||
description: FlowStageBinding Serializer
|
||||
required:
|
||||
|
@ -6762,6 +6788,10 @@ definitions:
|
|||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
policybindingmodel_ptr_id:
|
||||
title: Policybindingmodel ptr id
|
||||
type: string
|
||||
readOnly: true
|
||||
target:
|
||||
title: Target
|
||||
type: string
|
||||
|
@ -6770,6 +6800,8 @@ definitions:
|
|||
title: Stage
|
||||
type: string
|
||||
format: uuid
|
||||
stage_obj:
|
||||
$ref: '#/definitions/Stage'
|
||||
evaluate_on_plan:
|
||||
title: Evaluate on plan
|
||||
description: Evaluate policies during the Flow planning process. Disable this
|
||||
|
@ -6805,6 +6837,10 @@ definitions:
|
|||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
policybindingmodel_ptr_id:
|
||||
title: Policybindingmodel ptr id
|
||||
type: string
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
|
@ -8093,29 +8129,6 @@ definitions:
|
|||
\ log out manually. (Format: hours=1;minutes=2;seconds=3)."
|
||||
type: string
|
||||
minLength: 1
|
||||
Stage:
|
||||
description: Stage Serializer
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
title: Stage uuid
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
minLength: 1
|
||||
__type__:
|
||||
title: 'type '
|
||||
type: string
|
||||
readOnly: true
|
||||
verbose_name:
|
||||
title: Verbose name
|
||||
type: string
|
||||
readOnly: true
|
||||
CaptchaStage:
|
||||
description: CaptchaStage Serializer
|
||||
required:
|
||||
|
|
76
web/src/api/flow.ts
Normal file
76
web/src/api/flow.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { DefaultClient, PBResponse, QueryArguments } from "./client";
|
||||
|
||||
export enum FlowDesignation {
|
||||
Authentication = "authentication",
|
||||
Authorization = "authorization",
|
||||
Invalidation = "invalidation",
|
||||
Enrollment = "enrollment",
|
||||
Unrenollment = "unenrollment",
|
||||
Recovery = "recovery",
|
||||
StageConfiguration = "stage_configuration",
|
||||
}
|
||||
|
||||
export class Flow {
|
||||
pk: string;
|
||||
policybindingmodel_ptr_id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
designation: FlowDesignation;
|
||||
background: string;
|
||||
stages: string[];
|
||||
policies: string[];
|
||||
cache_count: number;
|
||||
|
||||
constructor() {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
static get(slug: string): Promise<Flow> {
|
||||
return DefaultClient.fetch<Flow>(["flows", "instances", slug]);
|
||||
}
|
||||
|
||||
static list(filter?: QueryArguments): Promise<PBResponse<Flow>> {
|
||||
return DefaultClient.fetch<PBResponse<Flow>>(["flows", "instances"], filter);
|
||||
}
|
||||
}
|
||||
|
||||
export class Stage {
|
||||
pk: string;
|
||||
name: string;
|
||||
__type__: string;
|
||||
verbose_name: string;
|
||||
|
||||
constructor() {
|
||||
throw Error();
|
||||
}
|
||||
}
|
||||
|
||||
export class FlowStageBinding {
|
||||
|
||||
pk: string;
|
||||
policybindingmodel_ptr_id: string;
|
||||
target: string;
|
||||
stage: string;
|
||||
stage_obj: Stage;
|
||||
evaluate_on_plan: boolean;
|
||||
re_evaluate_policies: boolean;
|
||||
order: number;
|
||||
policies: string[];
|
||||
|
||||
constructor() {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
static get(slug: string): Promise<FlowStageBinding> {
|
||||
return DefaultClient.fetch<FlowStageBinding>(["flows", "bindings", slug]);
|
||||
}
|
||||
|
||||
static list(filter?: QueryArguments): Promise<PBResponse<FlowStageBinding>> {
|
||||
return DefaultClient.fetch<PBResponse<FlowStageBinding>>(["flows", "bindings"], filter);
|
||||
}
|
||||
|
||||
static adminUrl(rest: string): string {
|
||||
return `/administration/stages/bindings/${rest}`;
|
||||
}
|
||||
}
|
|
@ -1,15 +1,33 @@
|
|||
import { DefaultClient, PBResponse, QueryArguments } from "./client";
|
||||
|
||||
export interface Policy {
|
||||
pk: string;
|
||||
name: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface PolicyBinding {
|
||||
export class PolicyBinding {
|
||||
pk: string;
|
||||
policy: string,
|
||||
policy: string;
|
||||
policy_obj: Policy;
|
||||
target: string;
|
||||
enabled: boolean;
|
||||
order: number;
|
||||
timeout: number;
|
||||
|
||||
constructor() {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
static get(pk: string): Promise<PolicyBinding> {
|
||||
return DefaultClient.fetch<PolicyBinding>(["policies", "bindings", pk]);
|
||||
}
|
||||
|
||||
static list(filter?: QueryArguments): Promise<PBResponse<PolicyBinding>> {
|
||||
return DefaultClient.fetch<PBResponse<PolicyBinding>>(["policies", "bindings"], filter);
|
||||
}
|
||||
|
||||
static adminUrl(rest: string): string {
|
||||
return `/administration/policies/bindings/${rest}`;
|
||||
}
|
||||
}
|
||||
|
|
22
web/src/api/source.ts
Normal file
22
web/src/api/source.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { DefaultClient, PBResponse, QueryArguments } from "./client";
|
||||
|
||||
export class Source {
|
||||
pk: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
enabled: boolean;
|
||||
authentication_flow: string;
|
||||
enrollment_flow: string;
|
||||
|
||||
constructor() {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
static get(slug: string): Promise<Source> {
|
||||
return DefaultClient.fetch<Source>(["sources", "all", slug]);
|
||||
}
|
||||
|
||||
static list(filter?: QueryArguments): Promise<PBResponse<Source>> {
|
||||
return DefaultClient.fetch<PBResponse<Source>>(["sources", "all"], filter);
|
||||
}
|
||||
}
|
|
@ -137,6 +137,14 @@ select[multiple] {
|
|||
--pf-c-table--BorderColor: var(--ak-dark-background-lighter);
|
||||
--pf-c-table--cell--Color: var(--ak-dark-foreground);
|
||||
}
|
||||
/* class for pagination text */
|
||||
.pf-c-options-menu__toggle {
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
/* table icon used for expanding rows */
|
||||
.pf-c-table__toggle-icon {
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
/* inputs */
|
||||
.pf-c-form-control {
|
||||
--pf-c-form-control--BorderTopColor: var(--ak-dark-background-lighter);
|
||||
|
@ -151,6 +159,13 @@ select[multiple] {
|
|||
background-color: var(--ak-dark-background-light);
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
.pf-c-button.pf-m-tertiary {
|
||||
--pf-c-button--after--BorderColor: var(--ak-dark-foreground-darker);
|
||||
color: var(--ak-dark-foreground-darker);
|
||||
}
|
||||
.pf-c-button.pf-m-tertiary:hover {
|
||||
--pf-c-button--after--BorderColor: var(--ak-dark-background-lighter);
|
||||
}
|
||||
.pf-c-form__label-text {
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
|
@ -162,6 +177,12 @@ select[multiple] {
|
|||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
/* modal */
|
||||
.pf-c-modal-box__header {
|
||||
background-color: var(--ak-dark-background-light);
|
||||
}
|
||||
.pf-c-modal-box__body {
|
||||
background-color: var(--ak-dark-background-light);
|
||||
}
|
||||
.pf-c-modal-box__footer {
|
||||
background-color: var(--ak-dark-background-light);
|
||||
}
|
||||
|
|
34
web/src/elements/EmptyState.ts
Normal file
34
web/src/elements/EmptyState.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { COMMON_STYLES } from "../common/styles";
|
||||
|
||||
@customElement("ak-empty-state")
|
||||
export class EmptyState extends LitElement {
|
||||
|
||||
@property({type: String})
|
||||
icon = "";
|
||||
|
||||
@property()
|
||||
header?: string;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-empty-state">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<i class="pf-icon ${this.icon} pf-c-empty-state__icon" aria-hidden="true"></i>
|
||||
<h1 class="pf-c-title pf-m-lg">
|
||||
${this.header}
|
||||
</h1>
|
||||
<div class="pf-c-empty-state__body">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<div class="pf-c-empty-state__primary">
|
||||
<slot name="primary"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
}
|
|
@ -104,6 +104,7 @@ export class ModalButton extends LitElement {
|
|||
this.dispatchEvent(
|
||||
new CustomEvent("ak-refresh", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
79
web/src/elements/policies/BoundPoliciesList.ts
Normal file
79
web/src/elements/policies/BoundPoliciesList.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { gettext } from "django";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { PBResponse } from "../../api/client";
|
||||
import { PolicyBinding } from "../../api/policy_binding";
|
||||
import { Table } from "../../elements/table/Table";
|
||||
|
||||
import "../../elements/Tabs";
|
||||
import "../../elements/AdminLoginsChart";
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
|
||||
@customElement("ak-bound-policies-list")
|
||||
export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
@property()
|
||||
target?: string;
|
||||
|
||||
apiEndpoint(page: number): Promise<PBResponse<PolicyBinding>> {
|
||||
return PolicyBinding.list({
|
||||
target: this.target || "",
|
||||
ordering: "order",
|
||||
page: page,
|
||||
});
|
||||
}
|
||||
|
||||
columns(): string[] {
|
||||
return ["Policy", "Enabled", "Order", "Timeout", ""];
|
||||
}
|
||||
|
||||
row(item: PolicyBinding): TemplateResult[] {
|
||||
return [
|
||||
html`${item.policy_obj.name}`,
|
||||
html`${item.enabled ? "Yes" : "No"}`,
|
||||
html`${item.order}`,
|
||||
html`${item.timeout}`,
|
||||
html`
|
||||
<ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/update/`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||
Edit
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/delete/`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||
Delete
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(html`<ak-empty-state header=${gettext("No Policies bound.")} icon="pf-icon-module">
|
||||
<div slot="body">
|
||||
${gettext("No policies are currently bound to this object.")}
|
||||
</div>
|
||||
<div slot="primary">
|
||||
<ak-modal-button href=${PolicyBinding.adminUrl(`create/?target=${this.target}`)}>
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
${gettext("Bind Policy")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
</div>
|
||||
</ak-empty-state>`);
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html`
|
||||
<ak-modal-button href=${PolicyBinding.adminUrl(`create/?target=${this.target}`)}>
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
${gettext("Bind Policy")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
${super.renderToolbar()}
|
||||
`;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import { Route } from "./Route";
|
|||
import { ROUTES } from "../../routes";
|
||||
import { RouteMatch } from "./RouteMatch";
|
||||
|
||||
import "../generic/SiteShell";
|
||||
import "../../pages/generic/SiteShell";
|
||||
|
||||
@customElement("ak-router-outlet")
|
||||
export class RouterOutlet extends LitElement {
|
|
@ -13,11 +13,58 @@ import { until } from "lit-html/directives/until";
|
|||
import "./SidebarBrand";
|
||||
import "./SidebarUser";
|
||||
|
||||
export interface SidebarItem {
|
||||
export class SidebarItem {
|
||||
name: string;
|
||||
path?: string[];
|
||||
children?: SidebarItem[];
|
||||
condition?: () => Promise<boolean>;
|
||||
path?: string;
|
||||
|
||||
_children: SidebarItem[];
|
||||
condition: () => Promise<boolean>;
|
||||
|
||||
activeMatchers: RegExp[];
|
||||
|
||||
constructor(name: string, path?: string) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this._children = [];
|
||||
this.condition = async () => true;
|
||||
this.activeMatchers = [];
|
||||
if (this.path) {
|
||||
this.activeMatchers.push(new RegExp(`^${this.path}$`));
|
||||
}
|
||||
}
|
||||
|
||||
children(...children: SidebarItem[]): SidebarItem {
|
||||
this._children = children;
|
||||
return this;
|
||||
}
|
||||
|
||||
activeWhen(...regexp: string[]): SidebarItem {
|
||||
regexp.forEach(r => {
|
||||
this.activeMatchers.push(new RegExp(r));
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
when(condition: () => Promise<boolean>): SidebarItem {
|
||||
this.condition = condition;
|
||||
return this;
|
||||
}
|
||||
|
||||
hasChildren(): boolean {
|
||||
return this._children.length > 0;
|
||||
}
|
||||
|
||||
isActive(activePath: string): boolean {
|
||||
if (!this.path) {
|
||||
return false;
|
||||
}
|
||||
return this.activeMatchers.some(v => {
|
||||
const match = v.exec(activePath);
|
||||
if (match !== null) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-sidebar")
|
||||
|
@ -78,9 +125,9 @@ export class Sidebar extends LitElement {
|
|||
return html``;
|
||||
}
|
||||
}
|
||||
return html` <li class="pf-c-nav__item ${item.children ? "pf-m-expandable pf-m-expanded" : ""}">
|
||||
return html` <li class="pf-c-nav__item ${item.hasChildren() ? "pf-m-expandable pf-m-expanded" : ""}">
|
||||
${item.path ?
|
||||
html`<a href="#${item.path}" class="pf-c-nav__link ${item.path.some((v) => v === this.activePath) ? "pf-m-current": ""}">
|
||||
html`<a href="#${item.path}" class="pf-c-nav__link ${item.isActive(this.activePath) ? "pf-m-current": ""}">
|
||||
${item.name}
|
||||
</a>` :
|
||||
html`<a class="pf-c-nav__link" aria-expanded="true">
|
||||
|
@ -91,7 +138,7 @@ export class Sidebar extends LitElement {
|
|||
</a>
|
||||
<section class="pf-c-nav__subnav">
|
||||
<ul class="pf-c-nav__simple-list">
|
||||
${item.children?.map((i) => until(this.renderItem(i), html``))}
|
||||
${item._children.map((i) => until(this.renderItem(i), html``))}
|
||||
</ul>
|
||||
</section>`}
|
||||
</li>`;
|
||||
|
|
|
@ -2,14 +2,22 @@ import { gettext } from "django";
|
|||
import { CSSResult, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { PBResponse } from "../../api/client";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
import { htmlFromString } from "../../utils";
|
||||
|
||||
import "./TablePagination";
|
||||
import "../EmptyState";
|
||||
|
||||
export abstract class Table<T> extends LitElement {
|
||||
abstract apiEndpoint(page: number): Promise<PBResponse<T>>;
|
||||
abstract columns(): Array<string>;
|
||||
abstract row(item: T): Array<string>;
|
||||
abstract row(item: T): Array<TemplateResult>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
renderExpanded(item: T): TemplateResult {
|
||||
if (this.expandable) {
|
||||
throw new Error("Expandable is enabled but renderExpanded is not overridden!");
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
|
||||
@property({attribute: false})
|
||||
data?: PBResponse<T>;
|
||||
|
@ -17,6 +25,12 @@ export abstract class Table<T> extends LitElement {
|
|||
@property({type: Number})
|
||||
page = 1;
|
||||
|
||||
@property({type: Boolean})
|
||||
expandable = false;
|
||||
|
||||
@property({attribute: false})
|
||||
expandedRows: boolean[] = [];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES;
|
||||
}
|
||||
|
@ -48,7 +62,7 @@ export abstract class Table<T> extends LitElement {
|
|||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
</div>
|
||||
<h2 class="pf-c-title pf-m-lg">Loading</h2>
|
||||
<h2 class="pf-c-title pf-m-lg">${gettext("Loading")}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,21 +70,59 @@ export abstract class Table<T> extends LitElement {
|
|||
</tr>`;
|
||||
}
|
||||
|
||||
renderEmpty(inner?: TemplateResult): TemplateResult {
|
||||
return html`<tbody role="rowgroup">
|
||||
<tr role="row">
|
||||
<td role="cell" colspan="8">
|
||||
<div class="pf-l-bullseye">
|
||||
${inner ? inner : html`<ak-empty-state header="none"></ak-empty-state>`}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>`;
|
||||
}
|
||||
|
||||
private renderRows(): TemplateResult[] | undefined {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
return this.data.results.map((item) => {
|
||||
const fullRow = ["<tr role=\"row\">"].concat(
|
||||
this.row(item).map((col) => {
|
||||
return `<td role="cell">${col}</td>`;
|
||||
})
|
||||
);
|
||||
fullRow.push("</tr>");
|
||||
return htmlFromString(...fullRow);
|
||||
if (this.data.pagination.count === 0) {
|
||||
return [this.renderEmpty()];
|
||||
}
|
||||
return this.data.results.map((item: T, idx: number) => {
|
||||
if ((this.expandedRows.length - 1) < idx) {
|
||||
this.expandedRows[idx] = false;
|
||||
}
|
||||
return html`<tbody role="rowgroup" class="${this.expandedRows[idx] ? "pf-m-expanded" : ""}">
|
||||
<tr role="row">
|
||||
${this.expandable ? html`<td class="pf-c-table__toggle" role="cell">
|
||||
<button class="pf-c-button pf-m-plain ${this.expandedRows[idx] ? "pf-m-expanded" : ""}" @click=${() => {
|
||||
this.expandedRows[idx] = !this.expandedRows[idx];
|
||||
this.requestUpdate();
|
||||
}}>
|
||||
<div class="pf-c-table__toggle-icon"> <i class="fas fa-angle-down" aria-hidden="true"></i> </div>
|
||||
</button>
|
||||
</td>` : html``}
|
||||
${this.row(item).map((col) => {
|
||||
return html`<td role="cell">${col}</td>`;
|
||||
})}
|
||||
</tr>
|
||||
<tr class="pf-c-table__expandable-row ${this.expandedRows[idx] ? "pf-m-expanded" : ""}" role="row">
|
||||
<td></td>
|
||||
${this.renderExpanded(item)}
|
||||
</tr>
|
||||
</tbody>`;
|
||||
});
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html` <button
|
||||
@click=${() => { this.fetch(); }}
|
||||
class="pf-c-button pf-m-primary">
|
||||
${gettext("Refresh")}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
renderTable(): TemplateResult {
|
||||
if (!this.data) {
|
||||
this.fetch();
|
||||
|
@ -78,12 +130,7 @@ export abstract class Table<T> extends LitElement {
|
|||
return html`<div class="pf-c-toolbar">
|
||||
<div class="pf-c-toolbar__content">
|
||||
<div class="pf-c-toolbar__bulk-select">
|
||||
<slot name="create-button"></slot>
|
||||
<button
|
||||
@click=${() => {this.fetch();}}
|
||||
class="pf-c-button pf-m-primary">
|
||||
${gettext("Refresh")}
|
||||
</button>
|
||||
${this.renderToolbar()}
|
||||
</div>
|
||||
<ak-table-pagination
|
||||
class="pf-c-toolbar__item pf-m-pagination"
|
||||
|
@ -92,15 +139,14 @@ export abstract class Table<T> extends LitElement {
|
|||
</ak-table-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-md">
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-md pf-m-expandable">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
${this.expandable ? html`<td role="cell">` : html``}
|
||||
${this.columns().map((col) => html`<th role="columnheader" scope="col">${gettext(col)}</th>`)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody role="rowgroup">
|
||||
${this.data ? this.renderRows() : this.renderLoading()}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pf-c-pagination pf-m-bottom">
|
||||
<ak-table-pagination
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<script src="/static/dist/main.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<ak-messages url="/api/v2beta/root/messages/"></ak-messages>
|
||||
<ak-messages></ak-messages>
|
||||
<div class="pf-c-page">
|
||||
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content"
|
||||
>Skip to content</a
|
||||
|
|
|
@ -1,120 +1,45 @@
|
|||
import { customElement } from "lit-element";
|
||||
import { User } from "../api/user";
|
||||
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
||||
import { SLUG_REGEX } from "../elements/router/Route";
|
||||
import { Interface } from "./Interface";
|
||||
|
||||
export const SIDEBAR_ITEMS: SidebarItem[] = [
|
||||
{
|
||||
name: "Library",
|
||||
path: ["/library/"],
|
||||
},
|
||||
{
|
||||
name: "Monitor",
|
||||
path: ["/audit/audit/"],
|
||||
condition: (): Promise<boolean> => {
|
||||
new SidebarItem("Library", "/library/"),
|
||||
new SidebarItem("Monitor", "/audit/audit").when((): Promise<boolean> => {
|
||||
return User.me().then(u => u.is_superuser);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Administration",
|
||||
children: [
|
||||
{
|
||||
name: "Overview",
|
||||
path: ["/administration/overview-ng/"],
|
||||
},
|
||||
{
|
||||
name: "System Tasks",
|
||||
path: ["/administration/tasks/"],
|
||||
},
|
||||
{
|
||||
name: "Applications",
|
||||
path: ["/administration/applications/"],
|
||||
},
|
||||
{
|
||||
name: "Sources",
|
||||
path: ["/administration/sources/"],
|
||||
},
|
||||
{
|
||||
name: "Providers",
|
||||
path: ["/administration/providers/"],
|
||||
},
|
||||
{
|
||||
name: "User Management",
|
||||
children: [
|
||||
{
|
||||
name: "User",
|
||||
path: ["/administration/users/"],
|
||||
},
|
||||
{
|
||||
name: "Groups",
|
||||
path: ["/administration/groups/"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Outposts",
|
||||
children: [
|
||||
{
|
||||
name: "Outposts",
|
||||
path: ["/administration/outposts/"],
|
||||
},
|
||||
{
|
||||
name: "Service Connections",
|
||||
path: ["/administration/outposts/service_connections/"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Policies",
|
||||
children: [
|
||||
{
|
||||
name: "Policies",
|
||||
path: ["/administration/policies/"],
|
||||
},
|
||||
{
|
||||
name: "Bindings",
|
||||
path: ["/administration/policies/bindings/"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Property Mappings",
|
||||
path: ["/administration/property-mappings/"],
|
||||
},
|
||||
{
|
||||
name: "Flows",
|
||||
children: [
|
||||
{
|
||||
name: "Flows",
|
||||
path: ["/administration/flows/"],
|
||||
},
|
||||
{
|
||||
name: "Stages",
|
||||
path: ["/administration/stages/"],
|
||||
},
|
||||
{
|
||||
name: "Prompts",
|
||||
path: ["/administration/stages/prompts/"],
|
||||
},
|
||||
{
|
||||
name: "Invitations",
|
||||
path: ["/administration/stages/invitations/"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Certificates",
|
||||
path: ["/administration/crypto/certificates/"],
|
||||
},
|
||||
{
|
||||
name: "Tokens",
|
||||
path: ["/administration/tokens/"],
|
||||
},
|
||||
],
|
||||
condition: (): Promise<boolean> => {
|
||||
}),
|
||||
new SidebarItem("Administration").children(
|
||||
new SidebarItem("Overview", "/administration/overview-ng/"),
|
||||
new SidebarItem("System Tasks", "/administration/tasks/"),
|
||||
new SidebarItem("Applications", "/administration/applications/").activeWhen(
|
||||
`^/applications/(?<slug>${SLUG_REGEX})/$`
|
||||
),
|
||||
new SidebarItem("Sources", "/administration/sources/").activeWhen(
|
||||
`^/sources/(?<slug>${SLUG_REGEX})/$`,
|
||||
),
|
||||
new SidebarItem("Providers", "/administration/providers/"),
|
||||
new SidebarItem("Flows").children(
|
||||
new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})/$`),
|
||||
new SidebarItem("Stages", "/administration/stages/"),
|
||||
new SidebarItem("Prompts", "/administration/stages/prompts/"),
|
||||
new SidebarItem("Invitations", "/administration/stages/invitations/"),
|
||||
),
|
||||
new SidebarItem("User Management").children(
|
||||
new SidebarItem("User", "/administration/users/"),
|
||||
new SidebarItem("Groups", "/administration/groups/")
|
||||
),
|
||||
new SidebarItem("Outposts").children(
|
||||
new SidebarItem("Outposts", "/administration/outposts/"),
|
||||
new SidebarItem("Service Connections", "/administration/outposts/service_connections/")
|
||||
),
|
||||
new SidebarItem("Policies", "/administration/policies/"),
|
||||
new SidebarItem("Property Mappings", "/administration/property-mappings"),
|
||||
new SidebarItem("Certificates", "/administration/crypto/certificates"),
|
||||
new SidebarItem("Tokens", "/administration/tokens/"),
|
||||
).when((): Promise<boolean> => {
|
||||
return User.me().then(u => u.is_superuser);
|
||||
},
|
||||
},
|
||||
})
|
||||
];
|
||||
|
||||
@customElement("ak-interface-admin")
|
||||
|
|
|
@ -3,7 +3,7 @@ import { html, LitElement, TemplateResult } from "lit-element";
|
|||
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
||||
|
||||
import "../elements/Messages";
|
||||
import "../pages/router/RouterOutlet";
|
||||
import "../elements/router/RouterOutlet";
|
||||
|
||||
export abstract class Interface extends LitElement {
|
||||
|
||||
|
|
|
@ -13,18 +13,18 @@ import "./elements/sidebar/SidebarUser";
|
|||
import "./elements/table/TablePagination";
|
||||
|
||||
import "./elements/AdminLoginsChart";
|
||||
import "./elements/EmptyState";
|
||||
import "./elements/cards/AggregateCard";
|
||||
import "./elements/cards/AggregatePromiseCard";
|
||||
import "./elements/CodeMirror";
|
||||
import "./elements/Messages";
|
||||
import "./elements/Spinner";
|
||||
import "./elements/Tabs";
|
||||
import "./elements/router/RouterOutlet";
|
||||
|
||||
import "./pages/generic/FlowShellCard";
|
||||
import "./pages/generic/SiteShell";
|
||||
|
||||
import "./pages/router/RouterOutlet";
|
||||
|
||||
import "./pages/admin-overview/AdminOverviewPage";
|
||||
import "./pages/admin-overview/TopApplicationsTable";
|
||||
import "./pages/applications/ApplicationListPage";
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { gettext } from "django";
|
||||
import { customElement } from "lit-element";
|
||||
import { customElement, html, TemplateResult } from "lit-element";
|
||||
import { Application } from "../../api/application";
|
||||
import { PBResponse } from "../../api/client";
|
||||
import { TablePage } from "../../elements/table/TablePage";
|
||||
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
|
||||
@customElement("ak-application-list")
|
||||
export class ApplicationList extends TablePage<Application> {
|
||||
pageTitle(): string {
|
||||
|
@ -27,13 +30,13 @@ export class ApplicationList extends TablePage<Application> {
|
|||
return ["Name", "Slug", "Provider", "Provider Type", ""];
|
||||
}
|
||||
|
||||
row(item: Application): string[] {
|
||||
row(item: Application): TemplateResult[] {
|
||||
return [
|
||||
item.name,
|
||||
item.slug,
|
||||
item.provider.toString(),
|
||||
item.provider.toString(),
|
||||
`
|
||||
html`${item.name}`,
|
||||
html`${item.slug}`,
|
||||
html`${item.provider}`,
|
||||
html`${item.provider}`,
|
||||
html`
|
||||
<ak-modal-button href="administration/policies/bindings/${item.pk}/update/">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||
Edit
|
||||
|
|
|
@ -1,54 +1,14 @@
|
|||
import { gettext } from "django";
|
||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { Application } from "../../api/application";
|
||||
import { DefaultClient, PBResponse } from "../../api/client";
|
||||
import { PolicyBinding } from "../../api/policy_binding";
|
||||
import { DefaultClient } from "../../api/client";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
import { Table } from "../../elements/table/Table";
|
||||
|
||||
import "../../elements/Tabs";
|
||||
import "../../elements/AdminLoginsChart";
|
||||
|
||||
@customElement("ak-bound-policies-list")
|
||||
export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
@property()
|
||||
target?: string;
|
||||
|
||||
apiEndpoint(page: number): Promise<PBResponse<PolicyBinding>> {
|
||||
return DefaultClient.fetch<PBResponse<PolicyBinding>>(["policies", "bindings"], {
|
||||
target: this.target || "",
|
||||
ordering: "order",
|
||||
page: page,
|
||||
});
|
||||
}
|
||||
|
||||
columns(): string[] {
|
||||
return ["Policy", "Enabled", "Order", "Timeout", ""];
|
||||
}
|
||||
|
||||
row(item: PolicyBinding): string[] {
|
||||
return [
|
||||
item.policy_obj.name,
|
||||
item.enabled ? "Yes" : "No",
|
||||
item.order.toString(),
|
||||
item.timeout.toString(),
|
||||
`
|
||||
<ak-modal-button href="administration/policies/bindings/${item.pk}/update/">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||
Edit
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-modal-button href="administration/policies/bindings/${item.pk}/delete/">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||
Delete
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import "../../elements/policies/BoundPoliciesList";
|
||||
|
||||
@customElement("ak-application-view")
|
||||
export class ApplicationViewPage extends LitElement {
|
||||
|
@ -108,7 +68,13 @@ export class ApplicationViewPage extends LitElement {
|
|||
</section>
|
||||
<div slot="page-2" data-tab-title="Policy Bindings" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<ak-bound-policies-list .target=${this.application.pk}></ak-bound-policies-list>
|
||||
<div class="pf-c-card__header">
|
||||
<div class="pf-c-card__header-main">
|
||||
${gettext("These policies control which users can access this application.")}
|
||||
</div>
|
||||
</div>
|
||||
<ak-bound-policies-list .target=${this.application.pk}>
|
||||
</ak-bound-policies-list>
|
||||
</div>
|
||||
</div>
|
||||
</ak-tabs>`;
|
||||
|
|
97
web/src/pages/flows/BoundStagesList.ts
Normal file
97
web/src/pages/flows/BoundStagesList.ts
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { gettext } from "django";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { PBResponse } from "../../api/client";
|
||||
import { Table } from "../../elements/table/Table";
|
||||
|
||||
import "../../elements/Tabs";
|
||||
import "../../elements/AdminLoginsChart";
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import "../../elements/policies/BoundPoliciesList";
|
||||
import { FlowStageBinding } from "../../api/flow";
|
||||
|
||||
@customElement("ak-bound-stages-list")
|
||||
export class BoundStagesList extends Table<FlowStageBinding> {
|
||||
expandable = true;
|
||||
|
||||
@property()
|
||||
target?: string;
|
||||
|
||||
apiEndpoint(page: number): Promise<PBResponse<FlowStageBinding>> {
|
||||
return FlowStageBinding.list({
|
||||
target: this.target || "",
|
||||
ordering: "order",
|
||||
page: page,
|
||||
});
|
||||
}
|
||||
|
||||
columns(): string[] {
|
||||
return ["Order", "Name", "Type", ""];
|
||||
}
|
||||
|
||||
row(item: FlowStageBinding): TemplateResult[] {
|
||||
return [
|
||||
html`${item.order}`,
|
||||
html`${item.stage_obj.name}`,
|
||||
html`${item.stage_obj.verbose_name}`,
|
||||
html`
|
||||
<ak-modal-button href="${FlowStageBinding.adminUrl(`${item.pk}/update/`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||
Edit
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-modal-button href="${FlowStageBinding.adminUrl(`${item.pk}/delete/`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-danger">
|
||||
Delete
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
renderExpanded(item: FlowStageBinding): TemplateResult {
|
||||
return html`
|
||||
<td></td>
|
||||
<td role="cell" colspan="3">
|
||||
<div class="pf-c-table__expandable-row-content">
|
||||
<div class="pf-c-content">
|
||||
<p>${gettext("These policies control when this stage will be applied to the flow.")}</p>
|
||||
<ak-bound-policies-list .target=${item.policybindingmodel_ptr_id}>
|
||||
</ak-bound-policies-list>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>`;
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(html`<ak-empty-state header=${gettext("No Stages bound")} icon="pf-icon-module">
|
||||
<div slot="body">
|
||||
${gettext("No stages are currently bound to this flow.")}
|
||||
</div>
|
||||
<div slot="primary">
|
||||
<ak-modal-button href="${FlowStageBinding.adminUrl(`create/?target=${this.target}`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
${gettext("Bind Stage")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
</div>
|
||||
</ak-empty-state>`);
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html`
|
||||
<ak-modal-button href="${FlowStageBinding.adminUrl(`create/?target=${this.target}`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
${gettext("Bind Stage")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
${super.renderToolbar()}
|
||||
`;
|
||||
}
|
||||
}
|
71
web/src/pages/flows/FlowViewPage.ts
Normal file
71
web/src/pages/flows/FlowViewPage.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { gettext } from "django";
|
||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
import { Flow } from "../../api/flow";
|
||||
|
||||
import "../../elements/Tabs";
|
||||
import "../../elements/AdminLoginsChart";
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import "../../elements/policies/BoundPoliciesList";
|
||||
import "./BoundStagesList";
|
||||
|
||||
@customElement("ak-flow-view")
|
||||
export class FlowViewPage extends LitElement {
|
||||
@property()
|
||||
set args(value: { [key: string]: string }) {
|
||||
this.flowSlug = value.slug;
|
||||
}
|
||||
|
||||
@property()
|
||||
set flowSlug(value: string) {
|
||||
Flow.get(value).then((flow) => (this.flow = flow));
|
||||
}
|
||||
|
||||
@property({attribute: false})
|
||||
flow?: Flow;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES.concat(
|
||||
css`
|
||||
img.pf-icon {
|
||||
max-height: 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.flow) {
|
||||
return html``;
|
||||
}
|
||||
return html`<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>
|
||||
<i class="pf-icon pf-icon-process-automation"></i>
|
||||
${this.flow?.name}
|
||||
</h1>
|
||||
<p>${this.flow?.title}</p>
|
||||
</div>
|
||||
</section>
|
||||
<ak-tabs>
|
||||
<div slot="page-2" data-tab-title="${gettext("Stage Bindings")}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<ak-bound-stages-list .target=${this.flow.pk}>
|
||||
</ak-bound-stages-list>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="page-3" data-tab-title="${gettext("Policy Bindings")}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header">
|
||||
<div class="pf-c-card__header-main">
|
||||
${gettext("These policies control which users can access this flow.")}
|
||||
</div>
|
||||
</div>
|
||||
<ak-bound-policies-list .target=${this.flow.policybindingmodel_ptr_id}>
|
||||
</ak-bound-policies-list>
|
||||
</div>
|
||||
</div>
|
||||
</ak-tabs>`;
|
||||
}
|
||||
}
|
63
web/src/pages/sources/SourceViewPage.ts
Normal file
63
web/src/pages/sources/SourceViewPage.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { gettext } from "django";
|
||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
|
||||
import "../../elements/Tabs";
|
||||
import "../../elements/AdminLoginsChart";
|
||||
import "../../elements/buttons/ModalButton";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import "../../elements/policies/BoundPoliciesList";
|
||||
import { Source } from "../../api/source";
|
||||
|
||||
@customElement("ak-source-view")
|
||||
export class SourceViewPage extends LitElement {
|
||||
@property()
|
||||
set args(value: { [key: string]: string }) {
|
||||
this.sourceSlug = value.slug;
|
||||
}
|
||||
|
||||
@property()
|
||||
set sourceSlug(value: string) {
|
||||
Source.get(value).then((source) => (this.source = source));
|
||||
}
|
||||
|
||||
@property({attribute: false})
|
||||
source?: Source;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES.concat(
|
||||
css`
|
||||
img.pf-icon {
|
||||
max-height: 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.source) {
|
||||
return html``;
|
||||
}
|
||||
return html`<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>
|
||||
<i class="pf-icon pf-icon-middleware"></i>
|
||||
${this.source?.name}
|
||||
</h1>
|
||||
</div>
|
||||
</section>
|
||||
<ak-tabs>
|
||||
<div slot="page-2" data-tab-title="Policy Bindings" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header">
|
||||
<div class="pf-c-card__header-main">
|
||||
${gettext("These policies control which users can access this application.")}
|
||||
</div>
|
||||
</div>
|
||||
<ak-bound-policies-list .target=${this.source.pk}>
|
||||
</ak-bound-policies-list>
|
||||
</div>
|
||||
</div>
|
||||
</ak-tabs>`;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
import { html } from "lit-html";
|
||||
import { Route, SLUG_REGEX } from "./pages/router/Route";
|
||||
import { Route, SLUG_REGEX } from "./elements/router/Route";
|
||||
|
||||
import "./pages/LibraryPage";
|
||||
import "./pages/admin-overview/AdminOverviewPage";
|
||||
import "./pages/applications/ApplicationListPage";
|
||||
import "./pages/applications/ApplicationViewPage";
|
||||
import "./pages/sources/SourceViewPage";
|
||||
import "./pages/flows/FlowViewPage";
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
// Prevent infinite Shell loops
|
||||
|
@ -16,4 +18,10 @@ export const ROUTES: Route[] = [
|
|||
new Route(new RegExp(`^/applications/(?<slug>${SLUG_REGEX})/$`)).then((args) => {
|
||||
return html`<ak-application-view .args=${args}></ak-application-view>`;
|
||||
}),
|
||||
new Route(new RegExp(`^/sources/(?<slug>${SLUG_REGEX})/$`)).then((args) => {
|
||||
return html`<ak-source-view .args=${args}></ak-source-view>`;
|
||||
}),
|
||||
new Route(new RegExp(`^/flows/(?<slug>${SLUG_REGEX})/$`)).then((args) => {
|
||||
return html`<ak-flow-view .args=${args}></ak-flow-view>`;
|
||||
}),
|
||||
];
|
||||
|
|
Reference in a new issue