diff --git a/authentik/admin/apps.py b/authentik/admin/apps.py
index db05f4daa..0c1da4d62 100644
--- a/authentik/admin/apps.py
+++ b/authentik/admin/apps.py
@@ -7,5 +7,4 @@ class AuthentikAdminConfig(AppConfig):
name = "authentik.admin"
label = "authentik_admin"
- mountpoint = "administration/"
verbose_name = "authentik Admin"
diff --git a/authentik/admin/templates/generic/create.html b/authentik/admin/templates/generic/create.html
deleted file mode 100644
index c1cbe91f1..000000000
--- a/authentik/admin/templates/generic/create.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends base_template|default:"generic/form.html" %}
-
-{% load authentik_utils %}
-{% load i18n %}
-
-{% block above_form %}
-
-
- {% block above_form %}
- {% endblock %}
-
-
-/update/",
- stages.StageUpdateView.as_view(),
- name="stage-update",
- ),
-]
diff --git a/authentik/admin/views/__init__.py b/authentik/admin/views/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/authentik/admin/views/stages.py b/authentik/admin/views/stages.py
deleted file mode 100644
index 39ff3ffbb..000000000
--- a/authentik/admin/views/stages.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""authentik Stage administration"""
-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.urls import reverse_lazy
-from django.utils.translation import gettext as _
-from guardian.mixins import PermissionRequiredMixin
-
-from authentik.admin.views.utils import InheritanceCreateView, InheritanceUpdateView
-from authentik.flows.models import Stage
-
-
-class StageCreateView(
- SuccessMessageMixin,
- LoginRequiredMixin,
- DjangoPermissionRequiredMixin,
- InheritanceCreateView,
-):
- """Create new Stage"""
-
- model = Stage
- template_name = "generic/create.html"
- permission_required = "authentik_flows.add_stage"
-
- success_url = reverse_lazy("authentik_core:if-admin")
- success_message = _("Successfully created Stage")
-
-
-class StageUpdateView(
- SuccessMessageMixin,
- LoginRequiredMixin,
- PermissionRequiredMixin,
- InheritanceUpdateView,
-):
- """Update stage"""
-
- model = Stage
- permission_required = "authentik_flows.update_application"
- template_name = "generic/update.html"
- success_url = reverse_lazy("authentik_core:if-admin")
- success_message = _("Successfully updated Stage")
diff --git a/authentik/admin/views/utils.py b/authentik/admin/views/utils.py
deleted file mode 100644
index 4c2fe7a38..000000000
--- a/authentik/admin/views/utils.py
+++ /dev/null
@@ -1,50 +0,0 @@
-"""authentik admin util views"""
-from typing import Any
-
-from django.http import Http404
-from django.views.generic import UpdateView
-
-from authentik.lib.utils.reflection import all_subclasses
-from authentik.lib.views import CreateAssignPermView
-
-
-class InheritanceCreateView(CreateAssignPermView):
- """CreateView for objects using InheritanceManager"""
-
- def get_form_class(self):
- provider_type = self.request.GET.get("type")
- try:
- model = next(
- x for x in all_subclasses(self.model) if x.__name__ == provider_type
- )
- except StopIteration as exc:
- raise Http404 from exc
- return model().form
-
- def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
- kwargs = super().get_context_data(**kwargs)
- form_cls = self.get_form_class()
- if hasattr(form_cls, "template_name"):
- kwargs["base_template"] = form_cls.template_name
- return kwargs
-
-
-class InheritanceUpdateView(UpdateView):
- """UpdateView for objects using InheritanceManager"""
-
- def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
- kwargs = super().get_context_data(**kwargs)
- form_cls = self.get_form_class()
- if hasattr(form_cls, "template_name"):
- kwargs["base_template"] = form_cls.template_name
- return kwargs
-
- def get_form_class(self):
- return self.get_object().form
-
- def get_object(self, queryset=None):
- return (
- self.model.objects.filter(pk=self.kwargs.get("pk"))
- .select_subclasses()
- .first()
- )
diff --git a/authentik/core/templates/partials/form_horizontal.html b/authentik/core/templates/partials/form_horizontal.html
deleted file mode 100644
index 14baa5286..000000000
--- a/authentik/core/templates/partials/form_horizontal.html
+++ /dev/null
@@ -1,115 +0,0 @@
-{% load authentik_utils %}
-{% load i18n %}
-
-{% csrf_token %}
-{% for field in form %}
-{% if field.field.widget|fieldtype == 'HiddenInput' %}
-{{ field }}
-{% else %}
-
-{% endif %}
-{% endfor %}
diff --git a/authentik/flows/api/stages.py b/authentik/flows/api/stages.py
index cbfc0c65f..d639a7f6b 100644
--- a/authentik/flows/api/stages.py
+++ b/authentik/flows/api/stages.py
@@ -1,7 +1,6 @@
"""Flow Stage API Views"""
from typing import Iterable
-from django.urls import reverse
from drf_yasg.utils import swagger_auto_schema
from rest_framework import mixins
from rest_framework.decorators import action
@@ -70,8 +69,7 @@ class StageViewSet(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
- "component": reverse("authentik_admin:stage-create")
- + f"?type={subclass.__name__}",
+ "component": subclass().component,
}
)
data = sorted(data, key=lambda x: x["name"])
diff --git a/authentik/flows/models.py b/authentik/flows/models.py
index 3db3db439..75c0bc930 100644
--- a/authentik/flows/models.py
+++ b/authentik/flows/models.py
@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, Optional, Type
from uuid import uuid4
from django.db import models
-from django.forms import ModelForm
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager
@@ -60,8 +59,8 @@ class Stage(SerializerModel):
raise NotImplementedError
@property
- def form(self) -> Type[ModelForm]:
- """Return Form class used to edit this object"""
+ def component(self) -> str:
+ """Return component used to edit this object"""
raise NotImplementedError
@property
diff --git a/authentik/flows/tests/test_models.py b/authentik/flows/tests/test_models.py
deleted file mode 100644
index 864518c39..000000000
--- a/authentik/flows/tests/test_models.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""flow model tests"""
-from typing import Callable, Type
-
-from django.forms import ModelForm
-from django.test import TestCase
-
-from authentik.flows.models import Stage
-from authentik.flows.stage import StageView
-
-
-class TestStageProperties(TestCase):
- """Generic model properties tests"""
-
-
-def stage_tester_factory(model: Type[Stage]) -> Callable:
- """Test a form"""
-
- def tester(self: TestStageProperties):
- model_inst = model()
- self.assertTrue(issubclass(model_inst.form, ModelForm))
- self.assertTrue(issubclass(model_inst.type, StageView))
-
- return tester
-
-
-for stage_type in Stage.__subclasses__():
- setattr(
- TestStageProperties,
- f"test_stage_{stage_type.__name__}",
- stage_tester_factory(stage_type),
- )
diff --git a/authentik/stages/deny/tests.py b/authentik/stages/deny/tests.py
index 22b4babc9..f87d4601a 100644
--- a/authentik/stages/deny/tests.py
+++ b/authentik/stages/deny/tests.py
@@ -9,7 +9,6 @@ from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
-from authentik.stages.deny.forms import DenyStageForm
from authentik.stages.deny.models import DenyStage
@@ -52,8 +51,3 @@ class TestUserDenyStage(TestCase):
"type": ChallengeTypes.NATIVE.value,
},
)
-
- def test_form(self):
- """Test Form"""
- data = {"name": "test"}
- self.assertEqual(DenyStageForm(data).is_valid(), True)
diff --git a/authentik/stages/dummy/tests.py b/authentik/stages/dummy/tests.py
index 02dd11876..b943d391e 100644
--- a/authentik/stages/dummy/tests.py
+++ b/authentik/stages/dummy/tests.py
@@ -5,7 +5,6 @@ from django.utils.encoding import force_str
from authentik.core.models import User
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
-from authentik.stages.dummy.forms import DummyStageForm
from authentik.stages.dummy.models import DummyStage
@@ -49,8 +48,3 @@ class TestDummyStage(TestCase):
force_str(response.content),
{"to": reverse("authentik_core:root-redirect"), "type": "redirect"},
)
-
- def test_form(self):
- """Test Form"""
- data = {"name": "test"}
- self.assertEqual(DummyStageForm(data).is_valid(), True)
diff --git a/web/src/api/legacy.ts b/web/src/api/legacy.ts
index 47d430d8d..871491be5 100644
--- a/web/src/api/legacy.ts
+++ b/web/src/api/legacy.ts
@@ -1,15 +1,3 @@
-export class AdminURLManager {
-
- static policies(rest: string): string {
- return `/administration/policies/${rest}`;
- }
-
- static stages(rest: string): string {
- return `/administration/stages/${rest}`;
- }
-
-}
-
export class AppURLManager {
static sourceSAML(slug: string, rest: string): string {
diff --git a/web/src/pages/stages/StageListPage.ts b/web/src/pages/stages/StageListPage.ts
index 2e760fabc..a9dedb57f 100644
--- a/web/src/pages/stages/StageListPage.ts
+++ b/web/src/pages/stages/StageListPage.ts
@@ -4,15 +4,34 @@ import { AKResponse } from "../../api/Client";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
-import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown";
import "../../elements/forms/DeleteForm";
+import "../../elements/forms/ProxyForm";
+import "../../elements/forms/ModalForm";
import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
import { Stage, StagesApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config";
-import { AdminURLManager } from "../../api/legacy";
+import { ifDefined } from "lit-html/directives/if-defined";
+
+import "./pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts";
+import "./pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts";
+import "./pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts";
+import "./pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts";
+import "./pages/stages/captcha/CaptchaStageForm.ts";
+import "./pages/stages/consent/ConsentStageForm.ts";
+import "./pages/stages/deny/DenyStageForm.ts";
+import "./pages/stages/dummy/DummyStageForm.ts";
+import "./pages/stages/email/EmailStageForm.ts";
+import "./pages/stages/identification/IdentificationStageForm.ts";
+import "./pages/stages/invitation/InvitationStageForm.ts";
+import "./pages/stages/password/PasswordStageForm.ts";
+import "./pages/stages/prompt/PromptStageForm.ts";
+import "./pages/stages/user_delete/UserDeleteStageForm.ts";
+import "./pages/stages/user_login/UserLoginStageForm.ts";
+import "./pages/stages/user_logout/UserLogoutStageForm.ts";
+import "./pages/stages/user_write/UserWriteStageForm.ts";
@customElement("ak-stage-list")
export class StageListPage extends TablePage {
@@ -61,12 +80,33 @@ export class StageListPage extends TablePage {
`;
})}`,
html`
-
-
+
+
+ ${gettext("Update")}
+
+
+ ${gettext(`Update ${item.verboseName}`)}
+
+
+
+
-
-
+
+
{
${until(new StagesApi(DEFAULT_CONFIG).stagesAllTypes().then((types) => {
return types.map((type) => {
return html`
-
-
`;
});
}), html``)}