Compare commits

..

1 Commits

Author SHA1 Message Date
Jens Langhammer
43628f308d initial steps for concurrent execution
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-10-03 19:51:57 +02:00
159 changed files with 3392 additions and 6440 deletions

View File

@@ -24,7 +24,6 @@ Makefile @goauthentik/infrastructure
.editorconfig @goauthentik/infrastructure
CODEOWNERS @goauthentik/infrastructure
# Backend packages
packages/django-channels-postgres @goauthentik/backend
packages/django-postgres-cache @goauthentik/backend
packages/django-dramatiq-postgres @goauthentik/backend
# Web packages

View File

@@ -76,7 +76,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.8.24 AS uv
FROM ghcr.io/astral-sh/uv:0.8.22 AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.7-slim-trixie-fips AS python-base

View File

@@ -15,7 +15,6 @@ from django.db.models import Model
from django.db.models.query_utils import Q
from django.db.transaction import atomic
from django.db.utils import IntegrityError
from django_channels_postgres.models import GroupChannel, Message
from guardian.models import UserObjectPermission
from guardian.shortcuts import assign_perm
from rest_framework.exceptions import ValidationError
@@ -138,8 +137,6 @@ def excluded_models() -> list[type[Model]]:
DeviceToken,
StreamEvent,
UserConsent,
Message,
GroupChannel,
)

View File

@@ -1,8 +0,0 @@
from authentik.blueprints.apps import ManagedAppConfig
class AuthentikCommandsConfig(ManagedAppConfig):
name = "authentik.commands"
label = "authentik_commands"
verbose_name = "authentik Commands"
default = True

View File

@@ -1,8 +0,0 @@
from django.db.migrations.autodetector import MigrationAutodetector as BaseMigrationAutodetector
from pgtrigger.migrations import MigrationAutodetectorMixin
MigrationAutodetector = type(
"MigrationAutodetector",
(MigrationAutodetectorMixin, BaseMigrationAutodetector),
{},
)

View File

@@ -1,7 +0,0 @@
from django.core.management.commands.makemigrations import Command as BaseCommand
from authentik.commands.management.commands import MigrationAutodetector
class Command(BaseCommand):
autodetector = MigrationAutodetector

View File

@@ -1,7 +0,0 @@
from django_tenants.management.commands.migrate import Command as BaseCommand
from authentik.commands.management.commands import MigrationAutodetector
class Command(BaseCommand):
autodetector = MigrationAutodetector # type: ignore[assignment]

View File

@@ -1,7 +0,0 @@
from django_tenants.management.commands.migrate_schemas import Command as BaseCommand
from authentik.commands.management.commands import MigrationAutodetector
class Command(BaseCommand):
autodetector = MigrationAutodetector # type: ignore[assignment]

View File

@@ -1,9 +1,13 @@
"""authentik shell command"""
import code
import platform
import sys
import traceback
from pprint import pprint
from django.core.management.commands.shell import Command as BaseCommand
from django.apps import apps
from django.core.management.base import BaseCommand
from django.db.models import Model
from django.db.models.signals import post_save, pre_delete
@@ -22,12 +26,29 @@ def get_banner_text(shell_type="shell") -> str:
class Command(BaseCommand):
"""Start the Django shell with all authentik models already imported"""
def get_namespace(self, **options):
return {
**super().get_namespace(**options),
django_models = {}
def add_arguments(self, parser):
parser.add_argument(
"-c",
"--command",
help="Python code to execute (instead of starting an interactive shell)",
)
def get_namespace(self):
"""Prepare namespace with all models"""
namespace = {
"pprint": pprint,
}
# Gather Django models and constants from each app
for app in apps.get_app_configs():
# Load models from each app
for model in app.get_models():
namespace[model.__name__] = model
return namespace
@staticmethod
def post_save_handler(sender, instance: Model, created: bool, **_):
"""Signal handler for all object's post_save"""
@@ -58,9 +79,41 @@ class Command(BaseCommand):
).save()
def handle(self, **options):
namespace = self.get_namespace()
post_save.connect(Command.post_save_handler)
pre_delete.connect(Command.pre_delete_handler)
print(get_banner_text())
# If Python code has been passed, execute it and exit.
if options["command"]:
super().handle(**options)
exec(options["command"], namespace) # nosec # noqa
return
try:
hook = sys.__interactivehook__
except AttributeError:
# Match the behavior of the cpython shell where a missing
# sys.__interactivehook__ is ignored.
pass
else:
try:
hook()
except Exception: # noqa
# Match the behavior of the cpython shell where an error in
# sys.__interactivehook__ prints a warning and the exception
# and continues.
print("Failed calling sys.__interactivehook__")
traceback.print_exc()
# Try to enable tab-complete
try:
import readline
import rlcompleter
except ModuleNotFoundError:
pass
else:
readline.set_completer(rlcompleter.Completer(namespace).complete)
readline.parse_and_bind("tab: complete")
# Run interactive shell
code.interact(banner=get_banner_text(), local=namespace)

View File

@@ -29,7 +29,6 @@ from authentik.blueprints.models import ManagedModel
from authentik.core.expression.exceptions import PropertyMappingExpressionException
from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.lib.avatars import get_avatar
from authentik.lib.config import CONFIG
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.generators import generate_id
from authentik.lib.merge import MERGE_LIST_UNIQUE
@@ -575,10 +574,8 @@ class Application(SerializerModel, PolicyBindingModel):
it is returned as-is"""
if not self.meta_icon:
return None
if self.meta_icon.name.startswith("http"):
if "://" in self.meta_icon.name or self.meta_icon.name.startswith("/static"):
return self.meta_icon.name
if self.meta_icon.name.startswith("/"):
return CONFIG.get("web.path", "/")[:-1] + self.meta_icon.name
return self.meta_icon.url
def get_launch_url(self, user: Optional["User"] = None) -> str | None:
@@ -780,10 +777,8 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
starts with http it is returned as-is"""
if not self.icon:
return None
if self.icon.name.startswith("http"):
if "://" in self.icon.name or self.icon.name.startswith("/static"):
return self.icon.name
if self.icon.name.startswith("/"):
return CONFIG.get("web.path", "/")[:-1] + self.icon.name
return self.icon.url
def get_user_path(self) -> str:

View File

@@ -4,7 +4,6 @@ from datetime import datetime, timedelta
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_channels_postgres.models import GroupChannel, Message
from django_postgres_cache.tasks import clear_expired_cache
from dramatiq.actor import actor
from structlog.stdlib import get_logger
@@ -35,8 +34,6 @@ def clean_expired_models():
LOGGER.debug("Expired models", model=cls, amount=amount)
self.info(f"Expired {amount} {cls._meta.verbose_name_plural}")
clear_expired_cache()
Message.delete_expired()
GroupChannel.delete_expired()
@actor(description=_("Remove temporary users created by SAML Sources."))

View File

@@ -82,51 +82,6 @@ class TestApplicationsAPI(APITestCase):
self.assertEqual(self.allowed.get_meta_icon, app["meta_icon"])
self.assertEqual(self.allowed.meta_icon.read(), b"text")
def test_set_icon_relative(self):
"""Test set_icon (relative path)"""
self.client.force_login(self.user)
response = self.client.post(
reverse(
"authentik_api:application-set-icon-url",
kwargs={"slug": self.allowed.slug},
),
data={"url": "relative/path"},
)
self.assertEqual(response.status_code, 200)
self.allowed.refresh_from_db()
self.assertEqual(self.allowed.get_meta_icon, "/media/public/relative/path")
def test_set_icon_absolute(self):
"""Test set_icon (absolute path)"""
self.client.force_login(self.user)
response = self.client.post(
reverse(
"authentik_api:application-set-icon-url",
kwargs={"slug": self.allowed.slug},
),
data={"url": "/relative/path"},
)
self.assertEqual(response.status_code, 200)
self.allowed.refresh_from_db()
self.assertEqual(self.allowed.get_meta_icon, "/relative/path")
def test_set_icon_url(self):
"""Test set_icon (url)"""
self.client.force_login(self.user)
response = self.client.post(
reverse(
"authentik_api:application-set-icon-url",
kwargs={"slug": self.allowed.slug},
),
data={"url": "https://authentik.company/img.png"},
)
self.assertEqual(response.status_code, 200)
self.allowed.refresh_from_db()
self.assertEqual(self.allowed.get_meta_icon, "https://authentik.company/img.png")
def test_check_access(self):
"""Test check_access operation"""
self.client.force_login(self.user)

View File

@@ -30,10 +30,7 @@ from authentik.policies.types import PolicyRequest
# Special keys which are *not* cleaned, even when the default filter
# is matched
ALLOWED_SPECIAL_KEYS = re.compile(
r"passing|password_change_date|^auth_method(_args)?$",
flags=re.I,
)
ALLOWED_SPECIAL_KEYS = re.compile("passing|password_change_date", flags=re.I)
def cleanse_item(key: str, value: Any) -> Any:

View File

@@ -54,6 +54,7 @@ class Challenge(PassiveSerializer):
flow_info = ContextualFlowInfo(required=False)
component = CharField(default="")
xid = CharField(required=False)
response_errors = DictField(
child=ErrorDetailSerializer(many=True), allow_empty=True, required=False

View File

@@ -190,7 +190,7 @@ class Flow(SerializerModel, PolicyBindingModel):
)
if self.background.name.startswith("http"):
return self.background.name
if self.background.name.startswith("/"):
if self.background.name.startswith("/static"):
return CONFIG.get("web.path", "/")[:-1] + self.background.name
return self.background.url

View File

@@ -143,10 +143,12 @@ class FlowPlan:
request: HttpRequest,
flow: Flow,
allowed_silent_types: list["StageView"] | None = None,
**get_params,
) -> HttpResponse:
"""Redirect to the flow executor for this flow plan"""
from authentik.flows.views.executor import (
SESSION_KEY_PLAN,
FlowContainer,
FlowExecutorView,
)
@@ -157,6 +159,7 @@ class FlowPlan:
# No unskippable stages found, so we can directly return the response of the last stage
final_stage: type[StageView] = self.bindings[-1].stage.view
temp_exec = FlowExecutorView(flow=flow, request=request, plan=self)
temp_exec.container = FlowContainer(request)
temp_exec.current_stage = self.bindings[-1].stage
temp_exec.current_stage_view = final_stage
temp_exec.setup(request, flow.slug)
@@ -174,6 +177,9 @@ class FlowPlan:
):
get_qs["inspector"] = "available"
for key, value in get_params:
get_qs[key] = value
return redirect_with_qs(
"authentik_core:if-flow",
get_qs,

View File

@@ -192,6 +192,7 @@ class ChallengeStageView(StageView):
)
flow_info.is_valid()
challenge.initial_data["flow_info"] = flow_info.data
challenge.initial_data["xid"] = self.executor.container.exec_id
if isinstance(challenge, WithUserInfoChallenge):
# If there's a pending user, update the `username` field
# this field is only used by password managers.

View File

@@ -29,7 +29,7 @@ window.authentik.flow = {
{% block body %}
<ak-skip-to-content></ak-skip-to-content>
<ak-message-container></ak-message-container>
<ak-flow-executor flowSlug="{{ flow.slug }}">
<ak-flow-executor flowSlug="{{ flow.slug }}" xid="{{ xid }}">
<ak-loading></ak-loading>
</ak-flow-executor>
{% endblock %}

View File

@@ -13,7 +13,6 @@ from authentik.core.models import Group, User
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.flows.markers import ReevaluateMarker, StageMarker
from authentik.flows.models import (
FlowAuthenticationRequirement,
FlowDeniedAction,
FlowDesignation,
FlowStageBinding,
@@ -178,25 +177,6 @@ class TestFlowExecutor(FlowTestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/unique-string")
@patch(
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,
)
def test_valid_flow_redirect_authenticated(self):
"""Test valid flow with valid redirect destination, authenticated already"""
flow = create_test_flow()
flow.designation = FlowDesignation.AUTHENTICATION
flow.authentication = FlowAuthenticationRequirement.REQUIRE_UNAUTHENTICATED
flow.save()
self.client.force_login(create_test_user())
dest = "/unique-string"
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
response = self.client.get(url + f"?{QS_QUERY}={urlencode({NEXT_ARG_NAME: dest})}")
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, "/unique-string")
@patch(
"authentik.flows.views.executor.to_stage_response",
TO_STAGE_RESPONSE_MOCK,

View File

@@ -1,6 +1,7 @@
"""authentik multi-stage authentication engine"""
from copy import deepcopy
from uuid import uuid4
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
@@ -63,6 +64,7 @@ from authentik.policies.engine import PolicyEngine
LOGGER = get_logger()
# Argument used to redirect user after login
NEXT_ARG_NAME = "next"
SESSION_KEY_PLAN_CONTAINER = "authentik/flows/plan_container/%s"
SESSION_KEY_PLAN = "authentik/flows/plan"
SESSION_KEY_APPLICATION_PRE = "authentik/flows/application_pre"
SESSION_KEY_GET = "authentik/flows/get"
@@ -70,6 +72,7 @@ SESSION_KEY_POST = "authentik/flows/post"
SESSION_KEY_HISTORY = "authentik/flows/history"
QS_KEY_TOKEN = "flow_token" # nosec
QS_QUERY = "query"
QS_EXEC_ID = "xid"
def challenge_types():
@@ -96,6 +99,88 @@ class InvalidStageError(SentryIgnoredException):
"""Error raised when a challenge from a stage is not valid"""
class FlowContainer:
"""Allow for multiple concurrent flow executions in the same session"""
def __init__(self, request: HttpRequest, exec_id: str | None = None) -> None:
self.request = request
self.exec_id = exec_id
@staticmethod
def new(request: HttpRequest):
exec_id = str(uuid4())
request.session[SESSION_KEY_PLAN_CONTAINER % exec_id] = {}
return FlowContainer(request, exec_id)
def exists(self) -> bool:
"""Check if flow exists in container/session"""
return SESSION_KEY_PLAN in self.session
def save(self):
self.request.session.modified = True
@property
def session(self):
# Backwards compatibility: store session plan/etc directly in session
if not self.exec_id:
return self.request.session
self.request.session.setdefault(SESSION_KEY_PLAN_CONTAINER % self.exec_id, {})
return self.request.session.get(SESSION_KEY_PLAN_CONTAINER % self.exec_id, {})
@property
def plan(self) -> FlowPlan:
return self.session.get(SESSION_KEY_PLAN)
def to_redirect(
self,
request: HttpRequest,
flow: Flow,
allowed_silent_types: list[StageView] | None = None,
**get_params,
) -> HttpResponse:
get_params[QS_EXEC_ID] = self.exec_id
return self.plan.to_redirect(
request, flow, allowed_silent_types=allowed_silent_types, **get_params
)
@plan.setter
def plan(self, value: FlowPlan):
self.session[SESSION_KEY_PLAN] = value
self.request.session.modified = True
self.save()
@property
def application_pre(self):
return self.session.get(SESSION_KEY_APPLICATION_PRE)
@property
def get(self) -> QueryDict:
return self.session.get(SESSION_KEY_GET)
@get.setter
def get(self, value: QueryDict):
self.session[SESSION_KEY_GET] = value
self.save()
@property
def post(self) -> QueryDict:
return self.session.get(SESSION_KEY_POST)
@post.setter
def post(self, value: QueryDict):
self.session[SESSION_KEY_POST] = value
self.save()
@property
def history(self) -> list[FlowPlan]:
return self.session.get(SESSION_KEY_HISTORY)
@history.setter
def history(self, value: list[FlowPlan]):
self.session[SESSION_KEY_HISTORY] = value
self.save()
@method_decorator(xframe_options_sameorigin, name="dispatch")
class FlowExecutorView(APIView):
"""Flow executor, passing requests to Stage Views"""
@@ -103,8 +188,9 @@ class FlowExecutorView(APIView):
permission_classes = [AllowAny]
flow: Flow = None
plan: FlowPlan | None = None
container: FlowContainer
current_binding: FlowStageBinding | None = None
current_stage: Stage
current_stage_view: View
@@ -160,10 +246,12 @@ class FlowExecutorView(APIView):
if QS_KEY_TOKEN in get_params:
plan = self._check_flow_token(get_params[QS_KEY_TOKEN])
if plan:
self.request.session[SESSION_KEY_PLAN] = plan
container = FlowContainer.new(request)
container.plan = plan
# Early check if there's an active Plan for the current session
if SESSION_KEY_PLAN in self.request.session:
self.plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
self.container = FlowContainer(request, request.GET.get(QS_EXEC_ID))
if self.container.exists():
self.plan: FlowPlan = self.container.plan
if self.plan.flow_pk != self.flow.pk.hex:
self._logger.warning(
"f(exec): Found existing plan for other flow, deleting plan",
@@ -176,21 +264,15 @@ class FlowExecutorView(APIView):
self._logger.debug("f(exec): Continuing existing plan")
# Initial flow request, check if we have an upstream query string passed in
request.session[SESSION_KEY_GET] = get_params
self.container.get = get_params
# Don't check session again as we've either already loaded the plan or we need to plan
if not self.plan:
request.session[SESSION_KEY_HISTORY] = []
self.container.history = []
self._logger.debug("f(exec): No active Plan found, initiating planner")
try:
self.plan = self._initiate_plan()
self.container.plan = self.plan
except FlowNonApplicableException as exc:
# If we're this flow is for authentication and the user is already authenticated
# continue to the next URL
if (
self.flow.designation == FlowDesignation.AUTHENTICATION
and self.request.user.is_authenticated
):
return self._flow_done()
self._logger.warning("f(exec): Flow not applicable to current user", exc=exc)
return self.handle_invalid_flow(exc)
except EmptyFlowException as exc:
@@ -262,12 +344,19 @@ class FlowExecutorView(APIView):
request=OpenApiTypes.NONE,
parameters=[
OpenApiParameter(
name="query",
name=QS_QUERY,
location=OpenApiParameter.QUERY,
required=True,
description="Querystring as received",
type=OpenApiTypes.STR,
)
),
OpenApiParameter(
name=QS_EXEC_ID,
location=OpenApiParameter.QUERY,
required=False,
description="Flow execution ID",
type=OpenApiTypes.STR,
),
],
operation_id="flows_executor_get",
)
@@ -294,8 +383,8 @@ class FlowExecutorView(APIView):
span.set_data("authentik Stage", self.current_stage_view)
span.set_data("authentik Flow", self.flow.slug)
stage_response = self.current_stage_view.dispatch(request)
return to_stage_response(request, stage_response)
except Exception as exc: # noqa
return to_stage_response(request, stage_response, self.container.exec_id)
except Exception as exc:
return self.handle_exception(exc)
@extend_schema(
@@ -313,12 +402,19 @@ class FlowExecutorView(APIView):
),
parameters=[
OpenApiParameter(
name="query",
name=QS_QUERY,
location=OpenApiParameter.QUERY,
required=True,
description="Querystring as received",
type=OpenApiTypes.STR,
)
),
OpenApiParameter(
name=QS_EXEC_ID,
location=OpenApiParameter.QUERY,
required=True,
description="Flow execution ID",
type=OpenApiTypes.STR,
),
],
operation_id="flows_executor_solve",
)
@@ -345,14 +441,15 @@ class FlowExecutorView(APIView):
span.set_data("authentik Stage", self.current_stage_view)
span.set_data("authentik Flow", self.flow.slug)
stage_response = self.current_stage_view.dispatch(request)
return to_stage_response(request, stage_response)
return to_stage_response(request, stage_response, self.container.exec_id)
except Exception as exc: # noqa
return self.handle_exception(exc)
def _initiate_plan(self) -> FlowPlan:
planner = FlowPlanner(self.flow)
plan = planner.plan(self.request)
self.request.session[SESSION_KEY_PLAN] = plan
container = FlowContainer.new(self.request)
container.plan = plan
try:
# Call the has_stages getter to check that
# there are no issues with the class we might've gotten
@@ -376,7 +473,7 @@ class FlowExecutorView(APIView):
except FlowNonApplicableException as exc:
self._logger.warning("f(exec): Flow restart not applicable to current user", exc=exc)
return self.handle_invalid_flow(exc)
self.request.session[SESSION_KEY_PLAN] = plan
self.container.plan = plan
kwargs = self.kwargs
kwargs.update({"flow_slug": self.flow.slug})
return redirect_with_qs("authentik_api:flow-executor", self.request.GET, **kwargs)
@@ -398,9 +495,13 @@ class FlowExecutorView(APIView):
)
self.cancel()
if next_param and not is_url_absolute(next_param):
return to_stage_response(self.request, redirect_with_qs(next_param))
return to_stage_response(
self.request, redirect_with_qs(next_param), self.container.exec_id
)
return to_stage_response(
self.request, self.stage_invalid(error_message=_("Invalid next URL"))
self.request,
self.stage_invalid(error_message=_("Invalid next URL")),
self.container.exec_id,
)
def stage_ok(self) -> HttpResponse:
@@ -414,7 +515,7 @@ class FlowExecutorView(APIView):
self.current_stage_view.cleanup()
self.request.session.get(SESSION_KEY_HISTORY, []).append(deepcopy(self.plan))
self.plan.pop()
self.request.session[SESSION_KEY_PLAN] = self.plan
self.container.plan = self.plan
if self.plan.bindings:
self._logger.debug(
"f(exec): Continuing with next stage",
@@ -457,6 +558,7 @@ class FlowExecutorView(APIView):
def cancel(self):
"""Cancel current flow execution"""
# TODO: Clean up container
keys_to_delete = [
SESSION_KEY_APPLICATION_PRE,
SESSION_KEY_PLAN,
@@ -479,8 +581,8 @@ class CancelView(View):
def get(self, request: HttpRequest) -> HttpResponse:
"""View which canels the currently active plan"""
if SESSION_KEY_PLAN in request.session:
del request.session[SESSION_KEY_PLAN]
if FlowContainer(request, request.GET.get(QS_EXEC_ID)).exists():
del request.session[SESSION_KEY_PLAN_CONTAINER % request.GET.get(QS_EXEC_ID)]
LOGGER.debug("Canceled current plan")
return redirect("authentik_flows:default-invalidation")
@@ -528,19 +630,12 @@ class ToDefaultFlow(View):
def dispatch(self, request: HttpRequest) -> HttpResponse:
flow = self.get_flow()
# If user already has a pending plan, clear it so we don't have to later.
if SESSION_KEY_PLAN in self.request.session:
plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
if plan.flow_pk != flow.pk.hex:
LOGGER.warning(
"f(def): Found existing plan for other flow, deleting plan",
flow_slug=flow.slug,
)
del self.request.session[SESSION_KEY_PLAN]
return redirect_with_qs("authentik_core:if-flow", request.GET, flow_slug=flow.slug)
get_qs = request.GET.copy()
get_qs[QS_EXEC_ID] = str(uuid4())
return redirect_with_qs("authentik_core:if-flow", get_qs, flow_slug=flow.slug)
def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpResponse:
def to_stage_response(request: HttpRequest, source: HttpResponse, xid: str) -> HttpResponse:
"""Convert normal HttpResponse into JSON Response"""
if (
isinstance(source, HttpResponseRedirect)
@@ -559,6 +654,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
RedirectChallenge(
{
"to": str(redirect_url),
"xid": xid,
}
)
)
@@ -567,6 +663,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
ShellChallenge(
{
"body": source.render().content.decode("utf-8"),
"xid": xid,
}
)
)
@@ -576,6 +673,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
ShellChallenge(
{
"body": source.content.decode("utf-8"),
"xid": xid,
}
)
)
@@ -607,4 +705,6 @@ class ConfigureFlowInitView(LoginRequiredMixin, View):
except FlowNonApplicableException:
LOGGER.warning("Flow not applicable to user")
raise Http404 from None
return plan.to_redirect(request, stage.configure_flow)
container = FlowContainer.new(request)
container.plan = plan
return container.to_redirect(request, stage.configure_flow)

View File

@@ -7,6 +7,7 @@ from ua_parser.user_agent_parser import Parse
from authentik.core.views.interface import InterfaceView
from authentik.flows.models import Flow
from authentik.flows.views.executor import QS_EXEC_ID
class FlowInterfaceView(InterfaceView):
@@ -17,6 +18,7 @@ class FlowInterfaceView(InterfaceView):
kwargs["flow"] = flow
kwargs["flow_background_url"] = flow.background_url(self.request)
kwargs["inspector"] = "inspector" in self.request.GET
kwargs["xid"] = self.request.GET.get(QS_EXEC_ID)
return super().get_context_data(**kwargs)
def compat_needs_sfe(self) -> bool:

View File

@@ -112,6 +112,7 @@ def get_logger_config():
"hpack": "WARNING",
"httpx": "WARNING",
"azure": "WARNING",
"channels_postgres": "WARNING",
}
for handler_name, level in handler_level_map.items():
base_config["loggers"][handler_name] = {

View File

@@ -68,7 +68,9 @@ class OutgoingSyncProviderStatusMixin:
return Response(SyncStatusSerializer(status).data)
last_task: Task = (
sync_schedule.tasks.filter(state__in=(TaskStatus.DONE, TaskStatus.REJECTED))
sync_schedule.tasks.exclude(
aggregated_status__in=(TaskStatus.CONSUMED, TaskStatus.QUEUED)
)
.order_by("-mtime")
.first()
)

View File

@@ -0,0 +1,35 @@
from typing import Any
from channels_postgres.core import PostgresChannelLayer as BasePostgresChannelLayer
from channels_postgres.db import DatabaseLayer as BaseDatabaseLayer
from django.conf import settings
from psycopg_pool import AsyncConnectionPool
from authentik.root.db.base import DatabaseWrapper
class DatabaseLayer(BaseDatabaseLayer):
async def get_db_pool(self, db_params: dict[str, Any]) -> AsyncConnectionPool:
db_wrapper = DatabaseWrapper(settings.CHANNEL_LAYERS["default"]["CONFIG"])
db_params = db_wrapper.get_connection_params()
db_params.pop("cursor_factory")
db_params.pop("context")
return await super().get_db_pool(db_params)
class PostgresChannelLayer(BasePostgresChannelLayer):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.django_db = DatabaseLayer(self.django_db.psycopg_options, self.db_params)
@property
def db_params(self):
db_wrapper = DatabaseWrapper(settings.CHANNEL_LAYERS["default"]["CONFIG"])
db_params = db_wrapper.get_connection_params()
db_params.pop("cursor_factory")
db_params.pop("context")
return db_params
@db_params.setter
def db_params(self, value):
pass

View File

@@ -51,7 +51,6 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Application definition
SHARED_APPS = [
"authentik.commands",
"django_tenants",
"authentik.tenants",
"django.contrib.messages",
@@ -65,7 +64,7 @@ SHARED_APPS = [
"pgactivity",
"pglock",
"channels",
"django_channels_postgres",
"channels_postgres",
"django_dramatiq_postgres",
"authentik.tasks",
]
@@ -305,7 +304,11 @@ DATABASE_ROUTERS = (
CHANNEL_LAYERS = {
"default": {
"BACKEND": "django_channels_postgres.layer.PostgresChannelLayer",
"BACKEND": "authentik.root.channels.PostgresChannelLayer",
"CONFIG": {
**DATABASES["default"],
"TIME_ZONE": None,
},
},
}
@@ -391,7 +394,6 @@ DRAMATIQ = {
"middlewares": (
("django_dramatiq_postgres.middleware.FullyQualifiedActorName", {}),
("django_dramatiq_postgres.middleware.DbConnectionMiddleware", {}),
("django_dramatiq_postgres.middleware.TaskStateBeforeMiddleware", {}),
("dramatiq.middleware.age_limit.AgeLimit", {}),
(
"dramatiq.middleware.time_limit.TimeLimit",
@@ -427,7 +429,6 @@ DRAMATIQ = {
"prefix": "authentik",
},
),
("django_dramatiq_postgres.middleware.TaskStateAfterMiddleware", {}),
),
"test": TEST,
}

View File

@@ -62,6 +62,11 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
"""Configure test environment settings"""
settings.TEST = True
settings.DRAMATIQ["test"] = True
settings.CHANNEL_LAYERS["default"]["CONFIG"] = {
**settings.DATABASES["default"],
**settings.DATABASES["default"]["TEST"],
"TIME_ZONE": None,
}
# Test-specific configuration
test_config = {

View File

@@ -108,7 +108,9 @@ class KerberosSourceViewSet(UsedByMixin, ModelViewSet):
return Response(SyncStatusSerializer(status).data)
last_task: Task = (
sync_schedule.tasks.filter(state__in=(TaskStatus.DONE, TaskStatus.REJECTED))
sync_schedule.tasks.exclude(
aggregated_status__in=(TaskStatus.CONSUMED, TaskStatus.QUEUED)
)
.order_by("-mtime")
.first()
)

View File

@@ -183,7 +183,9 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
return Response(SyncStatusSerializer(status).data)
last_task: Task = (
sync_schedule.tasks.filter(state__in=(TaskStatus.DONE, TaskStatus.REJECTED))
sync_schedule.tasks.exclude(
aggregated_status__in=(TaskStatus.CONSUMED, TaskStatus.QUEUED)
)
.order_by("-mtime")
.first()
)

View File

@@ -1,4 +1,3 @@
from django.db.models import Count
from django_dramatiq_postgres.models import TaskState
from django_filters.filters import BooleanFilter, MultipleChoiceFilter
from django_filters.filterset import FilterSet
@@ -7,9 +6,9 @@ from dramatiq.broker import get_broker
from dramatiq.errors import ActorNotFound
from dramatiq.message import Message
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import IntegerField, ReadOnlyField, SerializerMethodField
from rest_framework.fields import ReadOnlyField, SerializerMethodField
from rest_framework.mixins import (
ListModelMixin,
RetrieveModelMixin,
@@ -137,48 +136,3 @@ class TaskViewSet(
broker = get_broker()
broker.enqueue(Message.decode(task.message))
return Response(status=204)
@extend_schema(
request=OpenApiTypes.NONE,
responses={
200: inline_serializer(
"GlobalTaskStatusSerializer",
{
"queued": IntegerField(read_only=True),
"consumed": IntegerField(read_only=True),
"preprocess": IntegerField(read_only=True),
"running": IntegerField(read_only=True),
"postprocess": IntegerField(read_only=True),
"rejected": IntegerField(read_only=True),
"done": IntegerField(read_only=True),
"info": IntegerField(read_only=True),
"warning": IntegerField(read_only=True),
"error": IntegerField(read_only=True),
},
),
},
)
@action(detail=False, methods=["GET"], permission_classes=[])
def status(self, request: Request) -> Response:
"""Global status summary for all tasks"""
response = {}
for status in (
Task.objects.all()
.values("aggregated_status")
.annotate(count=Count("aggregated_status"))
):
response[status["aggregated_status"]] = status["count"]
return Response(
{
"queued": response.get("queued", 0),
"consumed": response.get("consumed", 0),
"preprocess": response.get("preprocess", 0),
"running": response.get("running", 0),
"postprocess": response.get("postprocess", 0),
"rejected": response.get("rejected", 0),
"done": response.get("done", 0),
"info": response.get("info", 0),
"warning": response.get("warning", 0),
"error": response.get("error", 0),
}
)

View File

@@ -63,7 +63,7 @@ class RelObjMiddleware(Middleware):
class MessagesMiddleware(Middleware):
def after_enqueue(self, broker: Broker, message: Message, delay: int | None):
def after_enqueue(self, broker: Broker, message: Message, delay: int):
task: Task = message.options["task"]
task_created: bool = message.options["task_created"]
if task_created:
@@ -107,13 +107,13 @@ class MessagesMiddleware(Middleware):
"Task finished processing without errors",
)
return
if should_ignore_exception(exception):
return
task.log(
class_to_path(type(self)),
TaskStatus.ERROR,
exception,
)
if should_ignore_exception(exception):
return
event_kwargs = {
"actor": task.actor_name,
}
@@ -235,7 +235,6 @@ class WorkerStatusMiddleware(Middleware):
sleep(10)
pass
@staticmethod
def keep(status: WorkerStatus):
lock_id = f"goauthentik.io/worker/status/{status.pk}"
with pglock.advisory(lock_id, side_effect=pglock.Raise):

View File

@@ -1,48 +0,0 @@
# Generated by Django 5.2.7 on 2025-10-07 13:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_tasks", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="task",
name="aggregated_status",
field=models.TextField(
choices=[
("queued", "Queued"),
("consumed", "Consumed"),
("preprocess", "Preprocess"),
("running", "Running"),
("postprocess", "Postprocess"),
("rejected", "Rejected"),
("done", "Done"),
("info", "Info"),
("warning", "Warning"),
("error", "Error"),
]
),
),
migrations.AlterField(
model_name="task",
name="state",
field=models.CharField(
choices=[
("queued", "Queued"),
("consumed", "Consumed"),
("preprocess", "Preprocess"),
("running", "Running"),
("postprocess", "Postprocess"),
("rejected", "Rejected"),
("done", "Done"),
],
default="queued",
help_text="Task status",
),
),
]

View File

@@ -19,9 +19,6 @@ class TaskStatus(models.TextChoices):
QUEUED = TaskState.QUEUED
CONSUMED = TaskState.CONSUMED
PREPROCESS = TaskState.PREPROCESS
RUNNING = TaskState.RUNNING
POSTPROCESS = TaskState.POSTPROCESS
REJECTED = TaskState.REJECTED
DONE = TaskState.DONE
INFO = "info"

View File

@@ -7479,7 +7479,6 @@
],
"enum": [
null,
"authentik.commands",
"authentik.tenants",
"authentik.tasks",
"authentik.admin",

6
go.mod
View File

@@ -8,7 +8,7 @@ require (
beryju.io/ldap v0.1.0
beryju.io/radius-eap v0.1.0
github.com/avast/retry-go/v4 v4.6.1
github.com/coreos/go-oidc/v3 v3.16.0
github.com/coreos/go-oidc/v3 v3.15.0
github.com/getsentry/sentry-go v0.35.3
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.12
@@ -51,7 +51,7 @@ require (
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.24.0 // indirect
@@ -96,3 +96,5 @@ require (
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace goauthentik.io/api/v3 => ./gen-go-api

8
go.sum
View File

@@ -18,8 +18,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -40,8 +40,8 @@ github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2D
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4=
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-07 00:10+0000\n"
"POT-Creation-Date: 2025-10-02 00:10+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -4006,22 +4006,6 @@ msgstr ""
msgid "Domains"
msgstr ""
#: packages/django-channels-postgres/django_channels_postgres/models.py
msgid "Group channel"
msgstr ""
#: packages/django-channels-postgres/django_channels_postgres/models.py
msgid "Group channels"
msgstr ""
#: packages/django-channels-postgres/django_channels_postgres/models.py
msgid "Message"
msgstr ""
#: packages/django-channels-postgres/django_channels_postgres/models.py
msgid "Messages"
msgstr ""
#: packages/django-dramatiq-postgres/django_dramatiq_postgres/models.py
msgid "Queue name"
msgstr ""

View File

@@ -1 +0,0 @@
# django-channels-postgres

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class DjangoChannelsPostgresConfig(AppConfig):
name = "django_channels_postgres"

View File

@@ -1,524 +0,0 @@
import asyncio
import functools
import types
import zlib
from base64 import b64decode
from contextlib import AbstractAsyncContextManager
from datetime import UTC, datetime, timedelta
from re import Pattern
from typing import Any, cast
from uuid import uuid4
import msgpack
from channels.layers import BaseChannelLayer
from django.db import DEFAULT_DB_ALIAS, connections
from django.utils.timezone import now
from psycopg import AsyncConnection, Notify, sql
from psycopg.conninfo import make_conninfo
from psycopg.errors import Error as PsycopgError
from psycopg_pool import AsyncConnectionPool
from structlog.stdlib import get_logger
from django_channels_postgres.models import NOTIFY_CHANNEL, GroupChannel, Message
LOGGER = get_logger()
GROUP_CHANNEL_TABLE = GroupChannel._meta.db_table
MESSAGE_TABLE = Message._meta.db_table
async def _async_proxy(
obj: "PostgresChannelLayerLoopProxy",
name: str,
*args: Any,
**kwargs: Any,
) -> Any:
# Must be defined as a function and not a method due to
# https://bugs.python.org/issue38364
layer = obj._get_layer()
return await getattr(layer, name)(*args, **kwargs)
def _wrap_close(proxy: "PostgresChannelLayerLoopProxy", loop: asyncio.AbstractEventLoop) -> None:
original_impl = loop.close
def _wrapper(self: asyncio.AbstractEventLoop, *args: Any, **kwargs: Any) -> None:
if loop in proxy._layers:
layer = proxy._layers[loop]
del proxy._layers[loop]
loop.run_until_complete(layer.flush())
self.close = original_impl # type: ignore[method-assign]
return self.close(*args, **kwargs)
loop.close = types.MethodType(_wrapper, loop) # type: ignore[method-assign]
class PostgresChannelLayerLoopProxy:
def __init__(
self,
*args: Any,
**kwargs: Any,
) -> None:
self._args = args
self._kwargs = kwargs
self._kwargs["channel_layer"] = self
self._layers: dict[asyncio.AbstractEventLoop, PostgresChannelLoopLayer] = {}
def __getattr__(self, name: str) -> Any:
if name in (
"new_channel",
"send",
"receive",
"group_add",
"group_discard",
"group_send",
"flush",
):
return functools.partial(_async_proxy, self, name)
else:
return getattr(self._get_layer(), name)
def serialize(self, message: dict[str, Any]) -> bytes:
"""Serializes message to a byte string."""
m = cast(bytes, msgpack.packb(message, use_bin_type=True))
c = zlib.compress(m, 6)
return c
def deserialize(self, message: bytes) -> dict[str, Any]:
"""Deserializes from a byte string."""
m = zlib.decompress(message)
return cast(dict[str, Any], msgpack.unpackb(m, raw=False))
def _get_layer(self) -> "PostgresChannelLoopLayer":
loop = asyncio.get_running_loop()
try:
layer = self._layers[loop]
except KeyError:
layer = PostgresChannelLoopLayer(*self._args, **self._kwargs)
self._layers[loop] = layer
_wrap_close(self, loop)
return layer
PostgresChannelLayer = PostgresChannelLayerLoopProxy
class PostgresChannelLoopLayer(BaseChannelLayer):
"""
Postgres channel layer.
It uses the NOTIFY/LISTEN functionality of postgres to broadcast messages
It also makes use of an internal message table to overcome the
8000bytes limit of Postgres' NOTIFY messages.
Which is a far cry from the channels standard of 1MB
This table has a trigger that sends out the `NOTIFY` signal.
Using a database also means messages are durable and will always be
available to consumers (as long as they're not expired).
"""
def __init__(
self,
channel_layer: PostgresChannelLayerLoopProxy,
prefix: str = "asgi",
expiry: int = 60,
group_expiry: int = 86400,
capacity: int = 100,
channel_capacity: dict[Pattern[str] | str, int] | None = None,
using: str = DEFAULT_DB_ALIAS,
) -> None:
super().__init__(expiry=expiry, capacity=capacity, channel_capacity=channel_capacity)
self.group_expiry = group_expiry
self.prefix = prefix
assert isinstance(self.prefix, str), "Prefix must be unicode" # nosec
self.channel_layer = channel_layer
self.using = using
self._pool_lock = asyncio.Lock()
# Each consumer gets its own *specific* channel, created with the `new_channel()` method.
# This dict maps `channel_name` to a queue of messages for that channel.
self.channels: dict[str, asyncio.Queue[tuple[str, bytes | None]]] = {}
self._pool: AsyncConnectionPool | None = None
self.receiver = PostgresChannelLayerReceiver(self.using, self)
def make_conninfo(self) -> str:
db_params = connections[self.using].get_connection_params()
# Prevent psycopg from using the custom synchronous cursor factory from django
db_params.pop("cursor_factory")
db_params.pop("context")
return make_conninfo(conninfo="", **db_params, connect_timeout=10)
async def connection(self) -> AbstractAsyncContextManager[AsyncConnection]:
if self._pool is None:
async with self._pool_lock:
async def _configure_connection(conn: AsyncConnection) -> None:
await conn.set_autocommit(True)
conn.prepare_threshold = 0 # All statements should be prepared
conn.prepared_max = None # No limit on the number of prepared statements
self._pool = AsyncConnectionPool(
conninfo=self.make_conninfo(),
open=False,
configure=_configure_connection,
min_size=1,
max_size=4,
)
await self._pool.open(wait=True)
return self._pool.connection()
async def _subscribe_to_channel(self, channel: str) -> None:
self.channels[channel] = asyncio.Queue()
await self.receiver.subscribe(channel)
extensions = ["groups", "flush"]
### Channel layer API ###
async def send(self, channel: str, message: dict[str, Any]) -> None:
"""
Send a message onto a (general or specific) channel.
"""
# Typecheck
assert isinstance(message, dict), "message is not a dict" # nosec
assert self.require_valid_channel_name(channel), "Channel name not valid" # nosec
# Make sure the message does not contain reserved keys
assert "__asgi_channel__" not in message # nosec
async with await self.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(
sql.SQL(
"""
INSERT INTO {table}
({id}, {channel}, {message}, {expires})
VALUES (%s, %s, %s, %s)
"""
).format(
table=sql.Identifier(MESSAGE_TABLE),
id=sql.Identifier("id"),
channel=sql.Identifier("channel"),
message=sql.Identifier("message"),
expires=sql.Identifier("expires"),
),
(
uuid4(),
channel,
self.channel_layer.serialize(message),
now() + timedelta(seconds=self.expiry),
),
)
async def new_channel(self, prefix: str = "specific") -> str:
"""
Returns a new channel name that can be used by something in our
process as a specific channel.
"""
channel = f"{self.prefix}.{prefix}.{uuid4().hex}"
await self._subscribe_to_channel(channel)
return channel
async def receive(self, channel: str) -> dict[str, Any]:
"""
Receive the first message that arrives on the channel.
If more than one coroutine waits on the same channel, the first waiter
will be given the message when it arrives.
"""
if channel not in self.channels:
await self._subscribe_to_channel(channel)
q = self.channels[channel]
try:
while True:
(message_id, message) = await q.get()
if message is None:
async with await self.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(
sql.SQL(
"""
SELECT {table}.{message}
FROM {table}
WHERE {table}.{id} = %s
"""
).format(
table=sql.Identifier(MESSAGE_TABLE),
id=sql.Identifier("id"),
message=sql.Identifier("message"),
),
(message_id,),
)
row = await cursor.fetchone()
if row is None:
continue
message = row[0]
break
except (asyncio.CancelledError, TimeoutError, GeneratorExit):
# We assume here that the reason we are cancelled is because the consumer
# is exiting, therefore we need to cleanup by unsubscribe below. Indeed,
# currently the way that Django Channels works, this is a safe assumption.
# In the future, Django Channels could change to call a *new* method that
# would serve as the antithesis of `new_channel()`; this new method might
# be named `delete_channel()`. If that were the case, we would do the
# following cleanup from that new `delete_channel()` method, but, since
# that's not how Django Channels works (yet), we do the cleanup below:
if channel in self.channels:
del self.channels[channel]
try:
await self.receiver.unsubscribe(channel)
except BaseException as exc: # noqa: BLE001
LOGGER.warning("Unexpected exception while cleaning-up channel", exc=exc)
# We don't re-raise here because we want the CancelledError to be the one
# re-raised
raise
return self.channel_layer.deserialize(message)
# ==============================================================
# Groups extension
# ==============================================================
async def group_add(self, group: str, channel: str) -> None:
"""
Adds the channel name to a group.
"""
# Check the inputs
assert self.require_valid_group_name(group), "Group name not valid" # nosec
assert self.require_valid_channel_name(channel), "Channel name not valid" # nosec
group_key = self._group_key(group)
async with await self.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(
sql.SQL(
"""
INSERT INTO {table}
({id}, {group_key}, {channel}, {expires})
VALUES (%s, %s, %s, %s)
"""
).format(
table=sql.Identifier(GROUP_CHANNEL_TABLE),
id=sql.Identifier("id"),
group_key=sql.Identifier("group_key"),
channel=sql.Identifier("channel"),
expires=sql.Identifier("expires"),
),
(
uuid4(),
group_key,
channel,
now() + timedelta(seconds=self.group_expiry),
),
)
async def group_discard(self, group: str, channel: str) -> None:
"""
Removes the channel from the named group if it is in the group;
does nothing otherwise (does not error)
"""
# Check the inputs
assert self.require_valid_group_name(group), "Group name not valid" # nosec
assert self.require_valid_channel_name(channel), "Channel name not valid" # nosec
group_key = self._group_key(group)
async with await self.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(
sql.SQL(
"""
DELETE
FROM {table}
WHERE {table}.{group_key} = %s
AND {table}.{channel} = %s
"""
).format(
table=sql.Identifier(GROUP_CHANNEL_TABLE),
group_key=sql.Identifier("group_key"),
channel=sql.Identifier("channel"),
),
(group_key, channel),
)
async def group_send(self, group: str, message: dict[str, Any]) -> None:
"""
Sends a message to the entire group.
"""
assert self.require_valid_group_name(group), "Group name not valid" # nosec
group_key = self._group_key(group)
serialized_message = self.channel_layer.serialize(message)
async with await self.connection() as conn:
async with conn.cursor() as cursor:
await cursor.execute(
sql.SQL(
"""
SELECT DISTINCT {table}.{channel}
FROM {table}
WHERE {table}.{group_key} = %s
"""
).format(
table=sql.Identifier(GROUP_CHANNEL_TABLE),
channel=sql.Identifier("channel"),
group_key=sql.Identifier("group_key"),
),
(group_key,),
)
channels = [row[0] for row in await cursor.fetchall()]
messages = [
(uuid4(), channel, serialized_message, now() + timedelta(seconds=self.expiry))
for channel in channels
]
async with conn.cursor() as cursor:
await cursor.executemany(
sql.SQL(
"""
INSERT INTO {table}
({id}, {channel}, {message}, {expires})
VALUES (%s, %s, %s, %s)
"""
).format(
table=sql.Identifier(MESSAGE_TABLE),
id=sql.Identifier("id"),
channel=sql.Identifier("channel"),
message=sql.Identifier("message"),
expires=sql.Identifier("expires"),
),
messages,
)
def _group_key(self, group: str) -> str:
"""
Common function to make the storage key for the group.
"""
return f"{self.prefix}.group.{group}"
### Flush extension ###
async def flush(self) -> None:
"""
Deletes all messages and groups.
"""
self.channels = {}
await self.receiver.flush()
class PostgresChannelLayerReceiver:
def __init__(self, using: str, channel_layer: PostgresChannelLoopLayer) -> None:
self.using = using
self.channel_layer = channel_layer
self._subscribed_to: set[str] = set()
self._lock = asyncio.Lock()
self._receive_task: asyncio.Task[None] | None = None
async def subscribe(self, channel: str) -> None:
async with self._lock:
if channel not in self._subscribed_to:
self._ensure_receiver()
self._subscribed_to.add(channel)
async def unsubscribe(self, channel: str) -> None:
async with self._lock:
if channel in self._subscribed_to:
self._ensure_receiver()
self._subscribed_to.remove(channel)
async def flush(self) -> None:
async with self._lock:
if self._receive_task is not None:
self._receive_task.cancel()
try:
await self._receive_task
except asyncio.CancelledError:
pass
self._receive_task = None
self._subscribed_to = set()
async def _do_receiving(self) -> None:
while True:
try:
async with await AsyncConnection.connect(
conninfo=self.channel_layer.make_conninfo(),
autocommit=True,
) as conn:
await self._process_backlog(conn)
await conn.execute(
sql.SQL("LISTEN {channel}").format(channel=sql.Identifier(NOTIFY_CHANNEL))
)
while True:
async for notify in conn.notifies(timeout=30):
await self._receive_notify(notify)
except (asyncio.CancelledError, TimeoutError, GeneratorExit):
raise
except PsycopgError as exc:
LOGGER.warning("Postgres connection is not healthy", exc=exc)
except BaseException as exc: # noqa: BLE001
LOGGER.warning("Unexpected exception in receive task", exc=exc, exc_info=True)
await asyncio.sleep(1)
async def _process_backlog(self, conn: AsyncConnection) -> None:
if not self._subscribed_to:
return
async with conn.cursor() as cursor:
await cursor.execute(
sql.SQL(
"""
DELETE
FROM {table}
WHERE {table}.{channel} IN (%s)
AND {table}.{expires} >= %s
RETURNING {table}.{id}, {table}.{channel}, {table}.{message}
"""
).format(
table=sql.Identifier(MESSAGE_TABLE),
id=sql.Identifier("id"),
channel=sql.Identifier("channel"),
expires=sql.Identifier("expires"),
message=sql.Identifier("message"),
),
(tuple(self._subscribed_to), now()),
)
async for row in cursor:
message_id, channel, message = row
self._receive_message(channel, message_id, message)
async def _receive_notify(self, notify: Notify) -> None:
payload = notify.payload
split_payload = payload.split(":")
message: bytes | None = None
match len(split_payload):
case 4:
message_id, channel, timestamp, base64_message = split_payload
if channel not in self._subscribed_to:
return
expires = datetime.fromtimestamp(float(timestamp), tz=UTC)
if expires < now():
return
message = b64decode(base64_message)
case 3:
message_id, channel, timestamp = split_payload
if channel not in self._subscribed_to:
return
expires = datetime.fromtimestamp(float(timestamp), tz=UTC)
if expires < now():
return
message = None
case _:
return
self._receive_message(channel, message_id, message)
def _receive_message(self, channel: str, message_id: str, message: bytes | None) -> None:
if (q := self.channel_layer.channels.get(channel)) is not None:
q.put_nowait((message_id, message))
def _ensure_receiver(self) -> None:
if self._receive_task is None:
self._receive_task = asyncio.ensure_future(self._do_receiving())

View File

@@ -1,94 +0,0 @@
# Generated by Django 5.1.13 on 2025-10-04 14:35
import django_channels_postgres.models
import pgtrigger.compiler
import pgtrigger.migrations
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="GroupChannel",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("group_key", models.TextField(db_index=True)),
("channel", models.TextField(db_index=True)),
(
"expires",
models.DateTimeField(
db_index=True, default=django_channels_postgres.models._default_group_expiry
),
),
],
options={
"verbose_name": "Group channel",
"verbose_name_plural": "Group channels",
"indexes": [
models.Index(
fields=["group_key", "channel"], name="django_chan_group_k_173f44_idx"
),
models.Index(
fields=["group_key", "expires"], name="django_chan_group_k_45d2e0_idx"
),
],
},
),
migrations.CreateModel(
name="Message",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
("channel", models.TextField(db_index=True)),
("message", models.BinaryField()),
(
"expires",
models.DateTimeField(
db_index=True,
default=django_channels_postgres.models._default_message_expiry,
),
),
],
options={
"verbose_name": "Message",
"verbose_name_plural": "Messages",
"indexes": [
models.Index(
fields=["channel", "expires"], name="django_chan_channel_e8ca51_idx"
)
],
},
),
pgtrigger.migrations.AddTrigger(
model_name="message",
trigger=pgtrigger.compiler.Trigger(
name="notify_new_channels_message",
sql=pgtrigger.compiler.UpsertTriggerSql(
constraint="CONSTRAINT",
declare="DECLARE payload text; encoded_message text; epoch text;",
func="\n encoded_message := encode(NEW.message, 'base64');\n epoch := extract(epoch from NEW.expires)::text;\n IF octet_length(NEW.id::text) + octet_length(NEW.channel) + octet_length(epoch) + octet_length(encoded_message) + 3 <= 8000 THEN\n payload := NEW.id::text || ':' || NEW.channel || ':' || epoch || ':' || encoded_message;\n ELSE\n payload := NEW.id::text || ':' || NEW.channel || ':' || epoch;\n END IF;\n\n PERFORM pg_notify('channels_messages', payload);\n RETURN NEW;\n ",
hash="cf7a665df0bbb7d865cdbc92b63d818fd25733d8",
operation="INSERT",
pgid="pgtrigger_notify_new_channels_message_d21ae",
table="django_channels_postgres_message",
timing="DEFERRABLE INITIALLY DEFERRED",
when="AFTER",
),
),
),
]

View File

@@ -1,97 +0,0 @@
from datetime import datetime, timedelta
from uuid import uuid4
import pgtrigger
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
def _default_group_expiry() -> datetime:
return now() + timedelta(seconds=86400)
def _default_message_expiry() -> datetime:
return now() + timedelta(minutes=1)
NOTIFY_CHANNEL = "channels_messages"
class GroupChannel(models.Model):
"""
A model that represents a group channel.
Groups are used to send messages to multiple channels.
"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
group_key = models.TextField(db_index=True)
channel = models.TextField(db_index=True)
expires = models.DateTimeField(db_index=True, default=_default_group_expiry)
class Meta:
verbose_name = _("Group channel")
verbose_name_plural = _("Group channels")
indexes = (
models.Index(fields=("group_key", "channel")),
models.Index(fields=("group_key", "expires")),
)
def __str__(self) -> str:
return f"Group '{self.group_key}' on channel '{self.channel}'"
@classmethod
def delete_expired(cls) -> None:
cls.objects.filter(expires__lt=now()).delete()
class Message(models.Model):
"""
A model that represents a message.
Messages are used to send messages to a specific channel.
E.g for user to user private messages.
"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
channel = models.TextField(db_index=True)
message = models.BinaryField()
expires = models.DateTimeField(db_index=True, default=_default_message_expiry)
class Meta:
verbose_name = _("Message")
verbose_name_plural = _("Messages")
indexes = (models.Index(fields=("channel", "expires")),)
triggers = (
pgtrigger.Trigger(
name="notify_new_channels_message",
operation=pgtrigger.Insert,
when=pgtrigger.After,
timing=pgtrigger.Deferred,
declare=[
("payload", "text"),
("encoded_message", "text"),
("epoch", "text"),
],
func=f"""
encoded_message := encode(NEW.message, 'base64');
epoch := extract(epoch from NEW.expires)::text;
IF octet_length(NEW.id::text) + octet_length(NEW.channel) + octet_length(epoch) + octet_length(encoded_message) + 3 <= 8000 THEN
payload := NEW.id::text || ':' || NEW.channel || ':' || epoch || ':' || encoded_message;
ELSE
payload := NEW.id::text || ':' || NEW.channel || ':' || epoch;
END IF;
PERFORM pg_notify('{NOTIFY_CHANNEL}', payload);
RETURN NEW;
""", # noqa: E501
),
)
def __str__(self) -> str:
return f"Message '{self.pk}' on channel '{self.channel}'"
@classmethod
def delete_expired(cls) -> None:
cls.objects.filter(expires__lt=now()).delete()

View File

@@ -1,52 +0,0 @@
[project]
name = "django-channels-postgres"
version = "0.1.0"
description = "Django channels layer using PostgreSQL NOTIFY/LISTEN"
requires-python = ">=3.9,<3.14"
readme = "README.md"
license = "MIT"
authors = [{ name = "Authentik Security Inc.", email = "hello@goauthentik.io" }]
keywords = ["django", "channels", "postgres"]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
"Framework :: Django :: 5.2",
"Intended Audience :: Developers",
"Operating System :: MacOS",
"Operating System :: POSIX",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
]
dependencies = [
"channels >=4.3,<4.4",
"django >=4.2,<6.0",
"django-pgtrigger >=4,<5",
"msgpack >=1,<2",
"psycopg[pool] >=3,<4",
"structlog >=25,<26",
]
[project.urls]
Homepage = "https://github.com/goauthentik/authentik/tree/main/packages/django-channels-postgres"
Documentation = "https://github.com/goauthentik/authentik/tree/main/packages/django-channels-postgres"
Repository = "https://github.com/goauthentik/authentik/tree/main/packages/django-channels-postgres"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.setuptools.packages]
find = {}

View File

@@ -209,11 +209,10 @@ class PostgresBroker(Broker):
if deadline and time.monotonic() >= deadline:
raise QueueJoinTimeout(queue_name) # type: ignore[no-untyped-call]
if (
not self.query_set.filter(queue_name=queue_name)
.exclude(state__in=(TaskState.DONE, TaskState.REJECTED))
.exists()
):
if self.query_set.filter(
queue_name=queue_name,
state__in=(TaskState.QUEUED, TaskState.CONSUMED),
).exists():
return
time.sleep(interval / 1000)
@@ -289,6 +288,7 @@ class _PostgresConsumer(Consumer):
self.query_set.filter(
message_id=message.message_id,
queue_name=message.queue_name,
state=TaskState.CONSUMED,
).update(
state=TaskState.DONE,
message=message.encode(),
@@ -303,6 +303,8 @@ class _PostgresConsumer(Consumer):
self.query_set.filter(
message_id=message.message_id,
queue_name=message.queue_name,
).exclude(
state=TaskState.REJECTED,
).update(
state=TaskState.REJECTED,
message=message.encode(),
@@ -326,9 +328,11 @@ class _PostgresConsumer(Consumer):
def _fetch_pending_notifies(self) -> list[Notify]:
self.logger.debug("Polling for lost messages", queue=self.queue_name)
notifies = (
self.query_set.filter(queue_name=self.queue_name)
self.query_set.filter(
state__in=(TaskState.QUEUED, TaskState.CONSUMED),
queue_name=self.queue_name,
)
.exclude(
state__in=(TaskState.DONE, TaskState.REJECTED),
message_id__in=self.in_processing,
)
.values_list("message_id", flat=True)
@@ -359,8 +363,10 @@ class _PostgresConsumer(Consumer):
return False
result = (
self.query_set.filter(message_id=message.message_id)
.exclude(state__in=(TaskState.DONE, TaskState.REJECTED))
self.query_set.filter(
message_id=message.message_id,
state__in=(TaskState.QUEUED, TaskState.CONSUMED),
)
.extra(
where=["pg_try_advisory_lock(%s)"],
params=[self._get_message_lock_id(message.message_id)],

View File

@@ -43,14 +43,12 @@ class Conf:
"middlewares",
(
("django_dramatiq_postgres.middleware.DbConnectionMiddleware", {}),
("django_dramatiq_postgres.middleware.TaskStateBeforeMiddleware", {}),
("dramatiq.middleware.age_limit.AgeLimit", {}),
("dramatiq.middleware.time_limit.TimeLimit", {}),
("dramatiq.middleware.shutdown.ShutdownNotifications", {}),
("dramatiq.middleware.callbacks.Callbacks", {}),
("dramatiq.middleware.pipelines.Pipelines", {}),
("dramatiq.middleware.retries.Retries", {}),
("django_dramatiq_postgres.middleware.TaskStateAfterMiddleware", {}),
),
),
)

View File

@@ -5,7 +5,7 @@ from collections.abc import Callable
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer as BaseHTTPServer
from ipaddress import IPv6Address, ip_address
from typing import TYPE_CHECKING, Any, cast
from typing import Any, cast
from django.db import DatabaseError, close_old_connections, connections
from dramatiq.actor import Actor
@@ -16,10 +16,7 @@ from dramatiq.middleware.middleware import Middleware
from structlog.stdlib import get_logger
from django_dramatiq_postgres.conf import Conf
from django_dramatiq_postgres.models import TaskBase, TaskState
if TYPE_CHECKING:
from django_dramatiq_postgres.broker import PostgresBroker
from django_dramatiq_postgres.models import TaskBase
class HTTPServer(BaseHTTPServer):
@@ -70,47 +67,6 @@ class DbConnectionMiddleware(Middleware):
before_worker_shutdown = _close_connections
class TaskStateBeforeMiddleware(Middleware):
def before_process_message(self, broker: "PostgresBroker", message: Message[Any]) -> None:
broker.query_set.filter(
message_id=message.message_id,
queue_name=message.queue_name,
state=TaskState.CONSUMED,
).update(
state=TaskState.PREPROCESS,
)
class TaskStateAfterMiddleware(Middleware):
def before_process_message(self, broker: "PostgresBroker", message: Message[Any]) -> None:
broker.query_set.filter(
message_id=message.message_id,
queue_name=message.queue_name,
state=TaskState.PREPROCESS,
).update(
state=TaskState.RUNNING,
)
def after_skip_message(self, broker: "PostgresBroker", message: Message[Any]) -> None:
broker.query_set.filter(
message_id=message.message_id,
queue_name=message.queue_name,
state=TaskState.RUNNING,
).update(
state=TaskState.POSTPROCESS,
)
def after_process_message(
self,
broker: "PostgresBroker",
message: Message[Any],
*,
result: Any | None = None,
exception: Exception | None = None,
) -> None:
self.after_skip_message(broker, message)
class FullyQualifiedActorName(Middleware):
def before_declare_actor(self, broker: Broker, actor: Actor[Any, Any]) -> None:
actor.actor_name = f"{actor.fn.__module__}.{actor.fn.__name__}"

View File

@@ -31,9 +31,6 @@ class TaskState(models.TextChoices):
QUEUED = "queued"
CONSUMED = "consumed"
PREPROCESS = "preprocess"
RUNNING = "running"
POSTPROCESS = "postprocess"
REJECTED = "rejected"
DONE = "done"

View File

@@ -33,7 +33,6 @@ classifiers = [
dependencies = [
"cron-converter >=1,<2",
"django >=4.2,<6.0",
"django-pglock >=1.7,<2",
"django-pgtrigger >=4,<5",
"dramatiq[watch] >=1.17,<1.18",
"tenacity >=9,<10",

View File

@@ -16,7 +16,7 @@
"@goauthentik/tsconfig": "^1.0.4",
"@types/node": "^24.3.1",
"esbuild": "^0.25.9",
"pino": "^10.0.0",
"pino": "^9.9.5",
"prettier": "^3.6.2",
"prettier-plugin-packagejson": "^2.5.19",
"typedoc": "^0.28.12",
@@ -905,13 +905,13 @@
}
},
"node_modules/@types/node": {
"version": "24.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz",
"integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==",
"version": "24.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.14.0"
"undici-types": "~7.13.0"
}
},
"node_modules/@types/unist": {
@@ -2294,9 +2294,9 @@
}
},
"node_modules/pino": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/pino/-/pino-10.0.0.tgz",
"integrity": "sha512-eI9pKwWEix40kfvSzqEP6ldqOoBIN7dwD/o91TY5z8vQI12sAffpR/pOqAD1IVVwIVHDpHjkq0joBPdJD0rafA==",
"version": "9.12.0",
"resolved": "https://registry.npmjs.org/pino/-/pino-9.12.0.tgz",
"integrity": "sha512-0Gd0OezGvqtqMwgYxpL7P0pSHHzTJ0Lx992h+mNlMtRVfNnqweWmf0JmRWk5gJzHalyd2mxTzKjhiNbGS2Ztfw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2904,9 +2904,9 @@
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==",
"dev": true,
"license": "MIT"
},

View File

@@ -39,7 +39,7 @@
"@goauthentik/tsconfig": "^1.0.4",
"@types/node": "^24.3.1",
"esbuild": "^0.25.9",
"pino": "^10.0.0",
"pino": "^9.9.5",
"prettier": "^3.6.2",
"prettier-plugin-packagejson": "^2.5.19",
"typedoc": "^0.28.12",

View File

@@ -122,6 +122,18 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
@@ -138,6 +150,27 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
"integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-member-expression-to-functions": "^7.27.1",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
"@babel/traverse": "^7.28.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
@@ -147,6 +180,19 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
"integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
@@ -177,6 +223,57 @@
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-optimise-call-expression": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
"integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
"integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-replace-supers": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
"integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
"license": "MIT",
"dependencies": {
"@babel/helper-member-expression-to-functions": "^7.27.1",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
"integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
@@ -232,6 +329,23 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-proposal-private-methods": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
"integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.18.6",
"@babel/helper-plugin-utils": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -382,21 +496,18 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz",
"integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.16.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
"integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
@@ -429,9 +540,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz",
"integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==",
"version": "9.36.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz",
"integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -449,12 +560,12 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
"integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.16.0",
"@eslint/core": "^0.15.2",
"levn": "^0.4.1"
},
"engines": {
@@ -729,17 +840,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
"integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz",
"integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.46.0",
"@typescript-eslint/type-utils": "8.46.0",
"@typescript-eslint/utils": "8.46.0",
"@typescript-eslint/visitor-keys": "8.46.0",
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/type-utils": "8.45.0",
"@typescript-eslint/utils": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -753,7 +864,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.46.0",
"@typescript-eslint/parser": "^8.45.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -769,16 +880,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz",
"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz",
"integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.0",
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/typescript-estree": "8.46.0",
"@typescript-eslint/visitor-keys": "8.46.0",
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"debug": "^4.3.4"
},
"engines": {
@@ -794,14 +905,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz",
"integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz",
"integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.46.0",
"@typescript-eslint/types": "^8.46.0",
"@typescript-eslint/tsconfig-utils": "^8.45.0",
"@typescript-eslint/types": "^8.45.0",
"debug": "^4.3.4"
},
"engines": {
@@ -816,14 +927,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz",
"integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz",
"integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/visitor-keys": "8.46.0"
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -834,9 +945,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz",
"integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz",
"integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -851,15 +962,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz",
"integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz",
"integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/typescript-estree": "8.46.0",
"@typescript-eslint/utils": "8.46.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/utils": "8.45.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -876,9 +987,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz",
"integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz",
"integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -890,16 +1001,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz",
"integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz",
"integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.46.0",
"@typescript-eslint/tsconfig-utils": "8.46.0",
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/visitor-keys": "8.46.0",
"@typescript-eslint/project-service": "8.45.0",
"@typescript-eslint/tsconfig-utils": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -958,16 +1069,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz",
"integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz",
"integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.46.0",
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/typescript-estree": "8.46.0"
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -982,13 +1093,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz",
"integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz",
"integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/types": "8.45.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -1332,9 +1443,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.12.tgz",
"integrity": "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==",
"version": "2.8.10",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz",
"integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
@@ -1466,9 +1577,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001748",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz",
"integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==",
"version": "1.0.30001746",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz",
"integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==",
"funding": [
{
"type": "opencollective",
@@ -1725,9 +1836,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.230",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.230.tgz",
"integrity": "sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==",
"version": "1.5.228",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz",
"integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==",
"license": "ISC"
},
"node_modules/es-abstract": {
@@ -1921,19 +2032,19 @@
}
},
"node_modules/eslint": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"version": "9.36.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.4.0",
"@eslint/core": "^0.16.0",
"@eslint/config-helpers": "^0.3.1",
"@eslint/core": "^0.15.2",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.37.0",
"@eslint/plugin-kit": "^0.4.0",
"@eslint/js": "9.36.0",
"@eslint/plugin-kit": "^0.3.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -2117,15 +2228,17 @@
}
},
"node_modules/eslint-plugin-react-hooks": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.1.tgz",
"integrity": "sha512-St9EKZzOAQF704nt2oJvAKZHjhrpg25ClQoaAlHmPZuajFldVLqRDW4VBNAS01NzeiQF0m0qhG1ZA807K6aVaQ==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.0.tgz",
"integrity": "sha512-72mucw/WLzEqGvL2vwE6fWR6geO6UbmDjz3eAb3pezxTpFzgbfyUeFKzmZKr9LhwUWMXfTVh1g0rKEJoyKNdoA==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.24.4",
"@babel/parser": "^7.24.4",
"zod": "^3.22.4 || ^4.0.0",
"zod-validation-error": "^3.0.3 || ^4.0.0"
"@babel/plugin-proposal-private-methods": "^7.18.6",
"hermes-parser": "^0.25.1",
"zod": "^3.22.4",
"zod-validation-error": "^3.0.3"
},
"engines": {
"node": ">=18"
@@ -2974,6 +3087,21 @@
"node": ">= 0.4"
}
},
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
"integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
"license": "MIT"
},
"node_modules/hermes-parser": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
"integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
"license": "MIT",
"dependencies": {
"hermes-estree": "0.25.1"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3783,9 +3911,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
"integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
"version": "2.0.21",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
"integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
"license": "MIT"
},
"node_modules/object-assign": {
@@ -5060,16 +5188,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.0.tgz",
"integrity": "sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz",
"integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@typescript-eslint/typescript-estree": "8.46.0",
"@typescript-eslint/utils": "8.46.0"
"@typescript-eslint/eslint-plugin": "8.45.0",
"@typescript-eslint/parser": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/utils": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"

View File

@@ -172,21 +172,18 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz",
"integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.16.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
"integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
@@ -231,9 +228,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz",
"integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==",
"version": "9.36.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz",
"integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -252,12 +249,12 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
"integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.16.0",
"@eslint/core": "^0.15.2",
"levn": "^0.4.1"
},
"engines": {
@@ -388,13 +385,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz",
"integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==",
"version": "24.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.14.0"
"undici-types": "~7.13.0"
}
},
"node_modules/@vue/compiler-core": {
@@ -692,19 +689,19 @@
}
},
"node_modules/eslint": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"version": "9.36.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.4.0",
"@eslint/core": "^0.16.0",
"@eslint/config-helpers": "^0.3.1",
"@eslint/core": "^0.15.2",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.37.0",
"@eslint/plugin-kit": "^0.4.0",
"@eslint/js": "9.36.0",
"@eslint/plugin-kit": "^0.3.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -1727,9 +1724,9 @@
}
},
"node_modules/undici-types": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==",
"dev": true,
"license": "MIT"
},

View File

@@ -7,12 +7,12 @@ requires-python = "==3.13.*"
dependencies = [
"argon2-cffi==25.1.0",
"channels==4.3.1",
"channels-postgres==1.1.2",
"cryptography==45.0.5",
"dacite==1.9.2",
"deepmerge==2.0",
"defusedxml==0.7.1",
"django==5.2.7",
"django-channels-postgres",
"django==5.1.13",
"django-countries==7.6.1",
"django-cte==2.0.0",
"django-dramatiq-postgres",
@@ -24,7 +24,7 @@ dependencies = [
"django-pgtrigger==4.15.2",
"django-prometheus==2.4.1",
"django-storages[s3]==1.14.6",
"django-tenants==3.9.0",
"django-tenants==3.8.0",
"djangoql==0.18.1",
"djangorestframework-guardian==0.4.0",
"djangorestframework==3.16.0",
@@ -79,7 +79,6 @@ dev = [
"aws-cdk-lib==2.188.0",
"bandit==1.8.3",
"black==25.1.0",
"bpython==0.25",
"channels[daphne]==4.3.1",
"codespell==2.4.1",
"colorama==0.4.6",
@@ -103,7 +102,6 @@ dev = [
"requests-mock==1.12.1",
"ruff==0.11.9",
"selenium==4.32.0",
"types-channels==4.3.0.20250822",
"types-ldap3==2.9.13.20250622",
]
@@ -121,14 +119,13 @@ no-binary-package = [
[tool.uv.sources]
djangorestframework = { git = "https://github.com/goauthentik/django-rest-framework", rev = "896722bab969fabc74a08b827da59409cf9f1a4e" }
django-channels-postgres = { workspace = true }
django-dramatiq-postgres = { workspace = true }
django-postgres-cache = { workspace = true }
opencontainers = { git = "https://github.com/vsoch/oci-python", rev = "ceb4fcc090851717a3069d78e85ceb1e86c2740c" }
channels-postgres = { git = "https://github.com/rissson/channels_postgres", rev = "93ed24e3c5317d7ccf7e8b1ce913c0f365d1728f" }
[tool.uv.workspace]
members = [
"packages/django-channels-postgres",
"packages/django-dramatiq-postgres",
"packages/django-postgres-cache",
]
@@ -208,11 +205,11 @@ plugins = ["mypy_django_plugin.main", "mypy_drf_plugin.main", "pydantic.mypy"]
exclude = ['^gen-py-api/']
[[tool.mypy.overrides]]
module = ["django_tenants.*", "dramatiq.*", "pglock.*"]
module = ["dramatiq.*", "pglock.*"]
follow_untyped_imports = true
[[tool.mypy.overrides]]
module = ["cron_converter.*", "msgpack.*"]
module = ["cron_converter.*"]
ignore_missing_imports = true
[[tool.mypy.overrides]]

View File

@@ -8138,6 +8138,11 @@ paths:
type: string
description: Querystring as received
required: true
- in: query
name: xid
schema:
type: string
description: Flow execution ID
tags:
- flows
security:
@@ -8178,6 +8183,12 @@ paths:
type: string
description: Querystring as received
required: true
- in: query
name: xid
schema:
type: string
description: Flow execution ID
required: true
tags:
- flows
requestBody:
@@ -38920,11 +38931,8 @@ paths:
- done
- error
- info
- postprocess
- preprocess
- queued
- rejected
- running
- warning
explode: true
style: form
@@ -38959,11 +38967,8 @@ paths:
enum:
- consumed
- done
- postprocess
- preprocess
- queued
- rejected
- running
description: |+
Task status
@@ -39053,33 +39058,6 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/tasks/tasks/status/:
get:
operationId: tasks_tasks_status_retrieve
description: Global status summary for all tasks
tags:
- tasks
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/GlobalTaskStatus'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/tasks/workers:
get:
operationId: tasks_workers_list
@@ -39607,6 +39585,8 @@ components:
component:
type: string
default: ak-stage-access-denied
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -39640,7 +39620,6 @@ components:
- name
AppEnum:
enum:
- authentik.commands
- authentik.tenants
- authentik.tasks
- authentik.admin
@@ -39730,6 +39709,8 @@ components:
component:
type: string
default: ak-source-oauth-apple
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -40065,6 +40046,8 @@ components:
component:
type: string
default: ak-stage-authenticator-duo
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -40221,6 +40204,8 @@ components:
component:
type: string
default: ak-stage-authenticator-email
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -40475,6 +40460,8 @@ components:
component:
type: string
default: ak-stage-authenticator-sms
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -40635,6 +40622,8 @@ components:
component:
type: string
default: ak-stage-authenticator-static
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -40753,6 +40742,8 @@ components:
component:
type: string
default: ak-stage-authenticator-totp
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -40977,6 +40968,8 @@ components:
component:
type: string
default: ak-stage-authenticator-validate
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -41030,6 +41023,8 @@ components:
component:
type: string
default: ak-stage-authenticator-webauthn
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -41192,6 +41187,8 @@ components:
component:
type: string
default: ak-stage-autosubmit
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -41486,6 +41483,8 @@ components:
component:
type: string
default: ak-stage-captcha
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -41900,6 +41899,8 @@ components:
component:
type: string
default: ak-stage-consent
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -42695,6 +42696,8 @@ components:
component:
type: string
default: ak-stage-dummy
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -42897,6 +42900,8 @@ components:
component:
type: string
default: ak-stage-email
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -43878,6 +43883,8 @@ components:
component:
type: string
default: ak-stage-flow-error
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -44206,6 +44213,8 @@ components:
component:
type: string
default: xak-flow-frame
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -44375,50 +44384,6 @@ components:
- bind_continent_country
- bind_continent_country_city
type: string
GlobalTaskStatus:
type: object
properties:
queued:
type: integer
readOnly: true
consumed:
type: integer
readOnly: true
preprocess:
type: integer
readOnly: true
running:
type: integer
readOnly: true
postprocess:
type: integer
readOnly: true
rejected:
type: integer
readOnly: true
done:
type: integer
readOnly: true
info:
type: integer
readOnly: true
warning:
type: integer
readOnly: true
error:
type: integer
readOnly: true
required:
- consumed
- done
- error
- info
- postprocess
- preprocess
- queued
- rejected
- running
- warning
GoogleWorkspaceProvider:
type: object
description: GoogleWorkspaceProvider Serializer
@@ -45220,6 +45185,8 @@ components:
component:
type: string
default: ak-stage-identification
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -46711,9 +46678,6 @@ components:
enum:
- queued
- consumed
- preprocess
- running
- postprocess
- rejected
- done
- info
@@ -47929,6 +47893,8 @@ components:
component:
type: string
default: ak-provider-oauth2-device-code
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -47957,6 +47923,8 @@ components:
component:
type: string
default: ak-provider-oauth2-device-code-finish
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -50752,6 +50720,8 @@ components:
component:
type: string
default: ak-stage-password
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -54711,6 +54681,8 @@ components:
component:
type: string
default: ak-source-plex
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -55236,6 +55208,8 @@ components:
component:
type: string
default: ak-stage-prompt
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -56449,6 +56423,8 @@ components:
component:
type: string
default: xak-flow-redirect
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -58494,6 +58470,8 @@ components:
component:
type: string
default: ak-stage-session-end
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -58664,6 +58642,8 @@ components:
component:
type: string
default: xak-flow-shell
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -59032,9 +59012,6 @@ components:
enum:
- queued
- consumed
- preprocess
- running
- postprocess
- rejected
- done
type: string
@@ -59303,9 +59280,6 @@ components:
enum:
- queued
- consumed
- preprocess
- running
- postprocess
- rejected
- done
- info
@@ -59357,6 +59331,8 @@ components:
component:
type: string
default: ak-source-telegram
xid:
type: string
response_errors:
type: object
additionalProperties:
@@ -60342,6 +60318,8 @@ components:
component:
type: string
default: ak-stage-user-login
xid:
type: string
response_errors:
type: object
additionalProperties:

View File

@@ -1,7 +1,7 @@
services:
chrome:
platform: linux/x86_64
image: docker.io/selenium/standalone-chrome:141.0
image: docker.io/selenium/standalone-chrome:140.0
volumes:
- /dev/shm:/dev/shm
network_mode: host

183
uv.lock generated
View File

@@ -1,11 +1,10 @@
version = 1
revision = 2
revision = 3
requires-python = "==3.13.*"
[manifest]
members = [
"authentik",
"django-channels-postgres",
"django-dramatiq-postgres",
"django-postgres-cache",
]
@@ -86,15 +85,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "ansicon"
version = "1.89.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b6/e2/1c866404ddbd280efedff4a9f15abfe943cb83cde6e895022370f3a61f85/ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1", size = 67312, upload-time = "2019-04-29T20:23:57.314Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/75/f9/f1c10e223c7b56a38109a3f2eb4e7fe9a757ea3ed3a166754fb30f65e466/ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec", size = 63675, upload-time = "2019-04-29T20:23:53.83Z" },
]
[[package]]
name = "anyio"
version = "4.11.0"
@@ -175,12 +165,12 @@ source = { editable = "." }
dependencies = [
{ name = "argon2-cffi" },
{ name = "channels" },
{ name = "channels-postgres" },
{ name = "cryptography" },
{ name = "dacite" },
{ name = "deepmerge" },
{ name = "defusedxml" },
{ name = "django" },
{ name = "django-channels-postgres" },
{ name = "django-countries" },
{ name = "django-cte" },
{ name = "django-dramatiq-postgres" },
@@ -247,7 +237,6 @@ dev = [
{ name = "aws-cdk-lib" },
{ name = "bandit" },
{ name = "black" },
{ name = "bpython" },
{ name = "channels", extra = ["daphne"] },
{ name = "codespell" },
{ name = "colorama" },
@@ -271,7 +260,6 @@ dev = [
{ name = "requests-mock" },
{ name = "ruff" },
{ name = "selenium" },
{ name = "types-channels" },
{ name = "types-ldap3" },
]
@@ -279,12 +267,12 @@ dev = [
requires-dist = [
{ name = "argon2-cffi", specifier = "==25.1.0" },
{ name = "channels", specifier = "==4.3.1" },
{ name = "channels-postgres", git = "https://github.com/rissson/channels_postgres?rev=93ed24e3c5317d7ccf7e8b1ce913c0f365d1728f" },
{ name = "cryptography", specifier = "==45.0.5" },
{ name = "dacite", specifier = "==1.9.2" },
{ name = "deepmerge", specifier = "==2.0" },
{ name = "defusedxml", specifier = "==0.7.1" },
{ name = "django", specifier = "==5.2.7" },
{ name = "django-channels-postgres", editable = "packages/django-channels-postgres" },
{ name = "django", specifier = "==5.1.13" },
{ name = "django-countries", specifier = "==7.6.1" },
{ name = "django-cte", specifier = "==2.0.0" },
{ name = "django-dramatiq-postgres", editable = "packages/django-dramatiq-postgres" },
@@ -296,7 +284,7 @@ requires-dist = [
{ name = "django-postgres-cache", editable = "packages/django-postgres-cache" },
{ name = "django-prometheus", specifier = "==2.4.1" },
{ name = "django-storages", extras = ["s3"], specifier = "==1.14.6" },
{ name = "django-tenants", specifier = "==3.9.0" },
{ name = "django-tenants", specifier = "==3.8.0" },
{ name = "djangoql", specifier = "==0.18.1" },
{ name = "djangorestframework", git = "https://github.com/goauthentik/django-rest-framework?rev=896722bab969fabc74a08b827da59409cf9f1a4e" },
{ name = "djangorestframework-guardian", specifier = "==0.4.0" },
@@ -351,7 +339,6 @@ dev = [
{ name = "aws-cdk-lib", specifier = "==2.188.0" },
{ name = "bandit", specifier = "==1.8.3" },
{ name = "black", specifier = "==25.1.0" },
{ name = "bpython", specifier = "==0.25" },
{ name = "channels", extras = ["daphne"], specifier = "==4.3.1" },
{ name = "codespell", specifier = "==2.4.1" },
{ name = "colorama", specifier = "==0.4.6" },
@@ -375,7 +362,6 @@ dev = [
{ name = "requests-mock", specifier = "==1.12.1" },
{ name = "ruff", specifier = "==0.11.9" },
{ name = "selenium", specifier = "==4.32.0" },
{ name = "types-channels", specifier = "==4.3.0.20250822" },
{ name = "types-ldap3", specifier = "==2.9.13.20250622" },
]
@@ -581,19 +567,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" },
]
[[package]]
name = "blessed"
version = "1.21.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinxed", marker = "sys_platform == 'win32'" },
{ name = "wcwidth" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0c/5e/3cada2f7514ee2a76bb8168c71f9b65d056840ebb711962e1ec08eeaa7b0/blessed-1.21.0.tar.gz", hash = "sha256:ece8bbc4758ab9176452f4e3a719d70088eb5739798cd5582c9e05f2a28337ec", size = 6660011, upload-time = "2025-04-26T21:56:58.199Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ea/8e/0a37e44878fd76fac9eff5355a1bf760701f53cb5c38cdcd59a8fd9ab2a2/blessed-1.21.0-py2.py3-none-any.whl", hash = "sha256:f831e847396f5a2eac6c106f4dfadedf46c4f804733574b15fe86d2ed45a9588", size = 84727, upload-time = "2025-04-26T16:58:29.919Z" },
]
[[package]]
name = "boto3"
version = "1.40.43"
@@ -622,23 +595,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/46/2eb4802e15e38befbea6cab7dafa1ab796722ab6f0833991c2a05e9f8ef0/botocore-1.40.43-py3-none-any.whl", hash = "sha256:1639f38999fc0cf42c92c5c83c5fbe189a4857a86f55b842be868e3283c6d3bb", size = 14057986, upload-time = "2025-10-01T19:38:13.714Z" },
]
[[package]]
name = "bpython"
version = "0.25"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "curtsies" },
{ name = "cwcwidth" },
{ name = "greenlet" },
{ name = "pygments" },
{ name = "pyxdg" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/dd/cc02bf66f342a4673867fdf6c1f9fce90ec1e91e651b21bc4af4890101da/bpython-0.25.tar.gz", hash = "sha256:c246fc909ef6dcc26e9d8cb4615b0e6b1613f3543d12269b19ffd0782166c65b", size = 207610, upload-time = "2025-01-17T09:35:22.382Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/74/5470df025854d5e213793b62cbea032fd66919562662955789fcc5dc17d6/bpython-0.25-py3-none-any.whl", hash = "sha256:28fd86008ca5ef6100ead407c9743aa60c51293a18ba5b18fcacea7f5b7f2257", size = 176131, upload-time = "2025-01-17T09:35:19.444Z" },
]
[[package]]
name = "cachetools"
version = "5.5.2"
@@ -728,6 +684,17 @@ daphne = [
{ name = "daphne" },
]
[[package]]
name = "channels-postgres"
version = "1.1.4"
source = { git = "https://github.com/rissson/channels_postgres?rev=93ed24e3c5317d7ccf7e8b1ce913c0f365d1728f#93ed24e3c5317d7ccf7e8b1ce913c0f365d1728f" }
dependencies = [
{ name = "asgiref" },
{ name = "channels" },
{ name = "msgpack" },
{ name = "psycopg", extra = ["pool"] },
]
[[package]]
name = "charset-normalizer"
version = "3.4.3"
@@ -877,34 +844,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" },
]
[[package]]
name = "curtsies"
version = "0.4.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "blessed" },
{ name = "cwcwidth" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/18/5741cb42624089a815520d5b65c39c3e59673a77fd1fab6ad65bdebf2f91/curtsies-0.4.3.tar.gz", hash = "sha256:102a0ffbf952124f1be222fd6989da4ec7cce04e49f613009e5f54ad37618825", size = 53401, upload-time = "2025-06-05T06:33:20.099Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/9b/b8ee3720d056309f4ab667bfc85995c4351f67b22e8c2008612b70350c3a/curtsies-0.4.3-py3-none-any.whl", hash = "sha256:65a1b4d6ff887bd9b0f0836cc6dc68c3a2c65c57f51a62f0ee5df408edee1a99", size = 35482, upload-time = "2025-06-05T06:33:19.122Z" },
]
[[package]]
name = "cwcwidth"
version = "0.1.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/23/76/03fc9fb3441a13e9208bb6103ebb7200eba7647d040008b8303a1c03e152/cwcwidth-0.1.10.tar.gz", hash = "sha256:7468760f72c1f4107be1b2b2854bc000401ea36a69daed36fb966a1e19a7a124", size = 60265, upload-time = "2025-02-09T21:15:28.452Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/af/f7/8c4cfe0b08053eea4da585ad5e12fef7cd11a0c9e4603ac8644c2a0b04b5/cwcwidth-0.1.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2391073280d774ab5d9af1d3aaa26ec456956d04daa1134fb71c31cd72ba5bba", size = 22344, upload-time = "2025-02-09T21:15:10.136Z" },
{ url = "https://files.pythonhosted.org/packages/2a/48/176bbaf56520c5d6b72cbbe0d46821989eaa30df628daa5baecdd7f35458/cwcwidth-0.1.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bfbdc2943631ec770ee781b35b8876fa7e283ff2273f944e2a9ae1f3df4ecdf", size = 94907, upload-time = "2025-02-09T21:15:11.178Z" },
{ url = "https://files.pythonhosted.org/packages/bc/fc/4dfed13b316a67bf2419a63db53566e3e5e4d4fc5a94ef493d3334be3c1f/cwcwidth-0.1.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb0103c7db8d86e260e016ff89f8f00ef5eb75c481abc346bfaa756da9f976b4", size = 100046, upload-time = "2025-02-09T21:15:12.279Z" },
{ url = "https://files.pythonhosted.org/packages/4e/83/612eecdeddbb1329d0f4d416f643459c2c5ae7b753490e31d9dccfa6deed/cwcwidth-0.1.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3b7d38c552edf663bf13f32310f9fd6661070409807b1b5bf89917e2de804ab1", size = 96143, upload-time = "2025-02-09T21:15:14.136Z" },
{ url = "https://files.pythonhosted.org/packages/57/98/87d10d88b5a6de3a4a3452802abed18b909510b9f118ad7f3a40ae48700a/cwcwidth-0.1.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1132be818498163a9208b9f4a28d759e08d3efeb885dfd10364434ccc7fa6a17", size = 99735, upload-time = "2025-02-09T21:15:15.216Z" },
{ url = "https://files.pythonhosted.org/packages/61/de/e0c02f84b0418db5938eeb1269f53dee195615a856ed12f370ef79f6cd5b/cwcwidth-0.1.10-cp313-cp313-win32.whl", hash = "sha256:dcead1b7b60c99f8cda249feb8059e4da38587c34d0b5f3aa130f13c62c0ce35", size = 22922, upload-time = "2025-02-09T21:15:16.999Z" },
{ url = "https://files.pythonhosted.org/packages/d8/4a/1d272a69f14924e43f5d6d4a7935c7a892e25c6e5b9a2c4459472132ef0c/cwcwidth-0.1.10-cp313-cp313-win_amd64.whl", hash = "sha256:b6eafd16d3edfec9acfc3d7b8856313bc252e0eccd56fb088f51ceed14c1bbdd", size = 25211, upload-time = "2025-02-09T21:15:17.898Z" },
]
[[package]]
name = "dacite"
version = "1.9.2"
@@ -970,39 +909,16 @@ wheels = [
[[package]]
name = "django"
version = "5.2.7"
version = "5.1.13"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "sqlparse" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/96/bd84e2bb997994de8bcda47ae4560991084e86536541d7214393880f01a8/django-5.2.7.tar.gz", hash = "sha256:e0f6f12e2551b1716a95a63a1366ca91bbcd7be059862c1b18f989b1da356cdd", size = 10865812, upload-time = "2025-10-01T14:22:12.081Z" }
sdist = { url = "https://files.pythonhosted.org/packages/bb/57/ad9905d03a2ee39064ee7ba69f8e2790db4a7ffaef9c54f95e7a8f2cb0a1/django-5.1.13.tar.gz", hash = "sha256:543ff21679f15e80edfc01fe7ea35f8291b6d4ea589433882913626a7c1cf929", size = 10742756, upload-time = "2025-10-01T14:25:41.187Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/ef/81f3372b5dd35d8d354321155d1a38894b2b766f576d0abffac4d8ae78d9/django-5.2.7-py3-none-any.whl", hash = "sha256:59a13a6515f787dec9d97a0438cd2efac78c8aca1c80025244b0fe507fe0754b", size = 8307145, upload-time = "2025-10-01T14:22:49.476Z" },
]
[[package]]
name = "django-channels-postgres"
version = "0.1.0"
source = { editable = "packages/django-channels-postgres" }
dependencies = [
{ name = "channels" },
{ name = "django" },
{ name = "django-pgtrigger" },
{ name = "msgpack" },
{ name = "psycopg", extra = ["pool"] },
{ name = "structlog" },
]
[package.metadata]
requires-dist = [
{ name = "channels", specifier = ">=4.3,<4.4" },
{ name = "django", specifier = ">=4.2,<6.0" },
{ name = "django-pgtrigger", specifier = ">=4,<5" },
{ name = "msgpack", specifier = ">=1,<2" },
{ name = "psycopg", extras = ["pool"], specifier = ">=3,<4" },
{ name = "structlog", specifier = ">=25,<26" },
{ url = "https://files.pythonhosted.org/packages/6c/f2/4b39467b74de9bb698c95232011e97dc848f490baae8d78c2e58848c4562/django-5.1.13-py3-none-any.whl", hash = "sha256:06f257f79dc4c17f3f9e23b106a4c5ed1335abecbe731e83c598c941d14fbeed", size = 8277515, upload-time = "2025-10-01T14:25:28.65Z" },
]
[[package]]
@@ -1037,7 +953,6 @@ source = { editable = "packages/django-dramatiq-postgres" }
dependencies = [
{ name = "cron-converter" },
{ name = "django" },
{ name = "django-pglock" },
{ name = "django-pgtrigger" },
{ name = "dramatiq", extra = ["watch"] },
{ name = "structlog" },
@@ -1048,7 +963,6 @@ dependencies = [
requires-dist = [
{ name = "cron-converter", specifier = ">=1,<2" },
{ name = "django", specifier = ">=4.2,<6.0" },
{ name = "django-pglock", specifier = ">=1.7,<2" },
{ name = "django-pgtrigger", specifier = ">=4,<5" },
{ name = "dramatiq", extras = ["watch"], specifier = ">=1.17,<1.18" },
{ name = "structlog", specifier = ">=25,<26" },
@@ -1204,15 +1118,12 @@ wheels = [
[[package]]
name = "django-tenants"
version = "3.9.0"
version = "3.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6b/54/c2e0e2dacd1caa9729c054da5bbdee4244ccd0de912b70dc2932a46ef177/django_tenants-3.9.0.tar.gz", hash = "sha256:6bc67c26be57f412e47c476261a977ef0ef74ec3740d50fd1eb6b69cb072eb03", size = 154244, upload-time = "2025-09-05T10:08:31.973Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/57/918cfca627fcdc3441981dddc72a22be02e57abdb5391eb7339ea77a5ef4/django_tenants-3.9.0-py3-none-any.whl", hash = "sha256:14421088a4336444e2c4af54f21a6af2e57e53dcf95ba5d19b5fa17142cb460b", size = 215955, upload-time = "2025-09-06T21:46:05.939Z" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/7b/22e3bb79d48e5a4fdcacdcdc27bbc5c2523a2b7892b440bfe229f313d823/django_tenants-3.8.0.tar.gz", hash = "sha256:07d009d5d01be2d65c3f5ddbf323d58d1228838fc1a64fded15c8e5c6f41cf8f", size = 154307, upload-time = "2025-05-23T16:07:24.307Z" }
[[package]]
name = "djangoql"
@@ -1824,18 +1735,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "jinxed"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ansicon", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/20/d0/59b2b80e7a52d255f9e0ad040d2e826342d05580c4b1d7d7747cfb8db731/jinxed-1.3.0.tar.gz", hash = "sha256:1593124b18a41b7a3da3b078471442e51dbad3d77b4d4f2b0c26ab6f7d660dbf", size = 80981, upload-time = "2024-07-31T22:39:18.854Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/e3/0e0014d6ab159d48189e92044ace13b1e1fe9aa3024ba9f4e8cf172aa7c2/jinxed-1.3.0-py2.py3-none-any.whl", hash = "sha256:b993189f39dc2d7504d802152671535b06d380b26d78070559551cbf92df4fc5", size = 33085, upload-time = "2024-07-31T22:39:17.426Z" },
]
[[package]]
name = "jmespath"
version = "1.0.1"
@@ -2905,15 +2804,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152, upload-time = "2025-03-17T00:56:07.819Z" },
]
[[package]]
name = "pyxdg"
version = "0.28"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/25/7998cd2dec731acbd438fbf91bc619603fc5188de0a9a17699a781840452/pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4", size = 77776, upload-time = "2022-06-05T11:35:01Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/8d/cf41b66a8110670e3ad03dab9b759704eeed07fa96e90fdc0357b2ba70e2/pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab", size = 49520, upload-time = "2022-06-05T11:34:58.832Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.2"
@@ -2931,6 +2821,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
]
[[package]]
name = "redis"
version = "6.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" },
]
[[package]]
name = "referencing"
version = "0.36.2"
@@ -3366,19 +3265,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/bb/d43e5c75054e53efce310e79d63df0ac3f25e34c926be5dffb7d283fb2a8/typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1", size = 17605, upload-time = "2021-12-10T21:09:37.844Z" },
]
[[package]]
name = "types-channels"
version = "4.3.0.20250822"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asgiref" },
{ name = "django-stubs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/22/3d/e0a164b7eaab18ab2302b7a3d90843824b729d9fe1d7bdbb3769dfc9d77b/types_channels-4.3.0.20250822.tar.gz", hash = "sha256:29a4928fdaed6d444b93b69d44fcdb5a8fe32fa72d6a41016c5d39fa7bd7f474", size = 15357, upload-time = "2025-08-22T03:04:26.444Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/52/4e3094e43d460feacb9051ec4c3498f8272f69d92b772647211478b25079/types_channels-4.3.0.20250822-py3-none-any.whl", hash = "sha256:d3fc0a1467c8cc901686826408c8a673822e07aa79cbe1a6d21946e7e55d9ddf", size = 21125, upload-time = "2025-08-22T03:04:25.539Z" },
]
[[package]]
name = "types-ldap3"
version = "2.9.13.20250622"
@@ -3609,15 +3495,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" },
]
[[package]]
name = "wcwidth"
version = "0.2.13"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" },
]
[[package]]
name = "webauthn"
version = "2.6.0"

417
web/package-lock.json generated
View File

@@ -17,7 +17,7 @@
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-python": "^6.2.1",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/legacy-modes": "^6.5.2",
"@codemirror/legacy-modes": "^6.5.1",
"@codemirror/theme-one-dark": "^6.1.3",
"@eslint/js": "^9.31.0",
"@floating-ui/dom": "^1.7.4",
@@ -44,7 +44,7 @@
"@patternfly/patternfly": "^4.224.2",
"@playwright/test": "^1.55.1",
"@sentry/browser": "^10.17.0",
"@spotlightjs/spotlight": "^4.1.3",
"@spotlightjs/spotlight": "^4.1.2",
"@storybook/addon-docs": "^9.1.10",
"@storybook/addon-links": "^9.1.10",
"@storybook/web-components": "^9.1.10",
@@ -53,7 +53,7 @@
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.4",
"@types/mocha": "^10.0.10",
"@types/node": "^24.7.0",
"@types/node": "^24.6.2",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@typescript-eslint/eslint-plugin": "^8.38.0",
@@ -72,7 +72,7 @@
"dompurify": "^3.2.7",
"esbuild": "^0.25.10",
"esbuild-plugin-copy": "^2.1.1",
"eslint": "^9.37.0",
"eslint": "^9.36.0",
"eslint-plugin-lit": "^2.1.1",
"eslint-plugin-wc": "^3.0.2",
"fuse.js": "^7.1.0",
@@ -108,7 +108,7 @@
"turnstile-types": "^1.2.3",
"type-fest": "^5.0.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.46.0",
"typescript-eslint": "^8.45.0",
"unist-util-visit": "^5.0.0",
"vite": "^7.1.9",
"vitest": "^3.2.4",
@@ -123,10 +123,10 @@
"@esbuild/darwin-arm64": "^0.25.4",
"@esbuild/linux-arm64": "^0.25.4",
"@esbuild/linux-x64": "^0.25.4",
"@rollup/rollup-darwin-arm64": "^4.52.4",
"@rollup/rollup-linux-arm64-gnu": "^4.52.4",
"@rollup/rollup-linux-x64-gnu": "^4.52.4",
"chromedriver": "^141.0.0",
"@rollup/rollup-darwin-arm64": "^4.52.3",
"@rollup/rollup-linux-arm64-gnu": "^4.52.3",
"@rollup/rollup-linux-x64-gnu": "^4.52.3",
"chromedriver": "^140.0.4",
"p-iteration": "^1.1.8"
}
},
@@ -542,9 +542,9 @@
}
},
"node_modules/@codemirror/legacy-modes": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz",
"integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==",
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.1.tgz",
"integrity": "sha512-DJYQQ00N1/KdESpZV7jg9hafof/iBNp9h7TYo1SLMk86TWl9uDsVdho2dzd81K+v4retmK6mdC7WpuOQDytQqw==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0"
@@ -1151,21 +1151,18 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz",
"integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.16.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
"integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
@@ -1232,9 +1229,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz",
"integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==",
"version": "9.36.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz",
"integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1252,12 +1249,12 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
"integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.16.0",
"@eslint/core": "^0.15.2",
"levn": "^0.4.1"
},
"engines": {
@@ -3201,9 +3198,9 @@
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "16.0.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.2.tgz",
"integrity": "sha512-tCtHJ2BlhSoK4cCs25NMXfV7EALKr0jyasmqVCq3y9cBrKdmJhtsy1iTz36Xhk/O+pDJbzawxF4K6ZblqCnITQ==",
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
"integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
@@ -3268,9 +3265,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
"integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
"cpu": [
"arm"
],
@@ -3281,9 +3278,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
"integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
"cpu": [
"arm64"
],
@@ -3294,9 +3291,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
"cpu": [
"arm64"
],
@@ -3307,9 +3304,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
"integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
"cpu": [
"x64"
],
@@ -3320,9 +3317,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
"integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
"cpu": [
"arm64"
],
@@ -3333,9 +3330,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
"integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
"cpu": [
"x64"
],
@@ -3346,9 +3343,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
"integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
"cpu": [
"arm"
],
@@ -3359,9 +3356,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
"integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
"cpu": [
"arm"
],
@@ -3372,9 +3369,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
"integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
"cpu": [
"arm64"
],
@@ -3385,9 +3382,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
"integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
"cpu": [
"arm64"
],
@@ -3398,9 +3395,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
"integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
"cpu": [
"loong64"
],
@@ -3411,9 +3408,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
"integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
"cpu": [
"ppc64"
],
@@ -3424,9 +3421,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
"integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
"cpu": [
"riscv64"
],
@@ -3437,9 +3434,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
"integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
"cpu": [
"riscv64"
],
@@ -3450,9 +3447,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
"integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
"cpu": [
"s390x"
],
@@ -3463,9 +3460,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
"integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
"cpu": [
"x64"
],
@@ -3476,9 +3473,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
"integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
"cpu": [
"x64"
],
@@ -3489,9 +3486,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
"integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
"cpu": [
"arm64"
],
@@ -3502,9 +3499,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
"integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
"cpu": [
"arm64"
],
@@ -3515,9 +3512,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
"integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
"cpu": [
"ia32"
],
@@ -3528,9 +3525,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
"integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
"integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
"cpu": [
"x64"
],
@@ -3541,9 +3538,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
"integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
"integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
"cpu": [
"x64"
],
@@ -3747,9 +3744,9 @@
}
},
"node_modules/@spotlightjs/overlay": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-4.2.0.tgz",
"integrity": "sha512-4bhsZHUpbtGjs5WI5Zl4WEGZ4/zngqZtcxloCzE578b0bY6fsPw0lnRbNAebNgk2fCuzAsPzf/KGaYwXVLkzeg==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-4.1.0.tgz",
"integrity": "sha512-/1xx4z/sYxMXNWVVaxXlf9R+p3eZjat6MFZ3vmT8BOu95/YpxHURO5JOPK6E0fm1QYbnrORpG0PvPTt+jutBzw==",
"license": "Apache-2.0"
},
"node_modules/@spotlightjs/sidecar": {
@@ -3778,13 +3775,13 @@
}
},
"node_modules/@spotlightjs/spotlight": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-4.1.3.tgz",
"integrity": "sha512-oO9zCDlERHkKJOIoXzVabXvxW3S4IekJnJx++7z5TSo+eaOSWCGY7XQYd9WfdsD+MfTj4dglc6dgqNvExR6Csw==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-4.1.2.tgz",
"integrity": "sha512-pwlqtXKGjXegjHrIPY+19W6F7RNhAEEPK75/qF+X1D1G/rPBMDLPIjyV7yngiLS6UetJdhNS46dhd0+KCWj9zg==",
"license": "Apache-2.0",
"dependencies": {
"@sentry/node": "^10",
"@spotlightjs/overlay": "4.2.0",
"@spotlightjs/overlay": "4.1.0",
"@spotlightjs/sidecar": "2.1.2",
"import-meta-resolve": "^4.1.0"
},
@@ -5403,18 +5400,18 @@
}
},
"node_modules/@types/node": {
"version": "24.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz",
"integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==",
"version": "24.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.14.0"
"undici-types": "~7.13.0"
}
},
"node_modules/@types/node/node_modules/undici-types": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
"integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz",
"integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==",
"license": "MIT"
},
"node_modules/@types/pg": {
@@ -5518,16 +5515,16 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.0.tgz",
"integrity": "sha512-hA8gxBq4ukonVXPy0OKhiaUh/68D0E88GSmtC1iAEnGaieuDi38LhS7jdCHRLi6ErJBNDGCzvh5EnzdPwUc0DA==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz",
"integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==",
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.46.0",
"@typescript-eslint/type-utils": "8.46.0",
"@typescript-eslint/utils": "8.46.0",
"@typescript-eslint/visitor-keys": "8.46.0",
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/type-utils": "8.45.0",
"@typescript-eslint/utils": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -5541,7 +5538,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.46.0",
"@typescript-eslint/parser": "^8.45.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -5556,15 +5553,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.0.tgz",
"integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz",
"integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.0",
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/typescript-estree": "8.46.0",
"@typescript-eslint/visitor-keys": "8.46.0",
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"debug": "^4.3.4"
},
"engines": {
@@ -5580,13 +5577,13 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.0.tgz",
"integrity": "sha512-OEhec0mH+U5Je2NZOeK1AbVCdm0ChyapAyTeXVIYTPXDJ3F07+cu87PPXcGoYqZ7M9YJVvFnfpGg1UmCIqM+QQ==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz",
"integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.46.0",
"@typescript-eslint/types": "^8.46.0",
"@typescript-eslint/tsconfig-utils": "^8.45.0",
"@typescript-eslint/types": "^8.45.0",
"debug": "^4.3.4"
},
"engines": {
@@ -5601,13 +5598,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.0.tgz",
"integrity": "sha512-lWETPa9XGcBes4jqAMYD9fW0j4n6hrPtTJwWDmtqgFO/4HF4jmdH/Q6wggTw5qIT5TXjKzbt7GsZUBnWoO3dqw==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz",
"integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/visitor-keys": "8.46.0"
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5618,9 +5615,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.0.tgz",
"integrity": "sha512-WrYXKGAHY836/N7zoK/kzi6p8tXFhasHh8ocFL9VZSAkvH956gfeRfcnhs3xzRy8qQ/dq3q44v1jvQieMFg2cw==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz",
"integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5634,14 +5631,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.0.tgz",
"integrity": "sha512-hy+lvYV1lZpVs2jRaEYvgCblZxUoJiPyCemwbQZ+NGulWkQRy0HRPYAoef/CNSzaLt+MLvMptZsHXHlkEilaeg==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz",
"integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/typescript-estree": "8.46.0",
"@typescript-eslint/utils": "8.46.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/utils": "8.45.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -5658,9 +5655,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.0.tgz",
"integrity": "sha512-bHGGJyVjSE4dJJIO5yyEWt/cHyNwga/zXGJbJJ8TiO01aVREK6gCTu3L+5wrkb1FbDkQ+TKjMNe9R/QQQP9+rA==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz",
"integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5671,15 +5668,15 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.0.tgz",
"integrity": "sha512-ekDCUfVpAKWJbRfm8T1YRrCot1KFxZn21oV76v5Fj4tr7ELyk84OS+ouvYdcDAwZL89WpEkEj2DKQ+qg//+ucg==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz",
"integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.46.0",
"@typescript-eslint/tsconfig-utils": "8.46.0",
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/visitor-keys": "8.46.0",
"@typescript-eslint/project-service": "8.45.0",
"@typescript-eslint/tsconfig-utils": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/visitor-keys": "8.45.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -5699,15 +5696,15 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.0.tgz",
"integrity": "sha512-nD6yGWPj1xiOm4Gk0k6hLSZz2XkNXhuYmyIrOWcHoPuAhjT9i5bAG+xbWPgFeNR8HPHHtpNKdYUXJl/D3x7f5g==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz",
"integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.46.0",
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/typescript-estree": "8.46.0"
"@typescript-eslint/scope-manager": "8.45.0",
"@typescript-eslint/types": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5722,12 +5719,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.0.tgz",
"integrity": "sha512-FrvMpAK+hTbFy7vH5j1+tMYHMSKLE6RzluFJlkFNKD0p9YsUT75JlBSmr5so3QRzvMwU5/bIEdeNrxm8du8l3Q==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz",
"integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.0",
"@typescript-eslint/types": "8.45.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -7371,9 +7368,9 @@
}
},
"node_modules/chromedriver": {
"version": "141.0.0",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-141.0.0.tgz",
"integrity": "sha512-w0U5jyWlLaRHV+dhaSikDz4x0qOwZcbles2HBu4oRdd+Eq7M43Uns4eoP/6dKu9Uc5ppcK9gA/E9GHROGXhgPg==",
"version": "140.0.4",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.4.tgz",
"integrity": "sha512-/NUoxYBNkJeoNj1B5ux3KxGShITlxJctkbApgVAa3ZC8EvCLKaBclwU3/IEj5MJHnBJzqOVDxs/eTyaF9k2fOg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"optional": true,
@@ -9121,19 +9118,19 @@
}
},
"node_modules/eslint": {
"version": "9.37.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
"version": "9.36.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.4.0",
"@eslint/core": "^0.16.0",
"@eslint/config-helpers": "^0.3.1",
"@eslint/core": "^0.15.2",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.37.0",
"@eslint/plugin-kit": "^0.4.0",
"@eslint/js": "9.36.0",
"@eslint/plugin-kit": "^0.3.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -16072,9 +16069,9 @@
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -16087,28 +16084,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.4",
"@rollup/rollup-android-arm64": "4.52.4",
"@rollup/rollup-darwin-arm64": "4.52.4",
"@rollup/rollup-darwin-x64": "4.52.4",
"@rollup/rollup-freebsd-arm64": "4.52.4",
"@rollup/rollup-freebsd-x64": "4.52.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
"@rollup/rollup-linux-arm-musleabihf": "4.52.4",
"@rollup/rollup-linux-arm64-gnu": "4.52.4",
"@rollup/rollup-linux-arm64-musl": "4.52.4",
"@rollup/rollup-linux-loong64-gnu": "4.52.4",
"@rollup/rollup-linux-ppc64-gnu": "4.52.4",
"@rollup/rollup-linux-riscv64-gnu": "4.52.4",
"@rollup/rollup-linux-riscv64-musl": "4.52.4",
"@rollup/rollup-linux-s390x-gnu": "4.52.4",
"@rollup/rollup-linux-x64-gnu": "4.52.4",
"@rollup/rollup-linux-x64-musl": "4.52.4",
"@rollup/rollup-openharmony-arm64": "4.52.4",
"@rollup/rollup-win32-arm64-msvc": "4.52.4",
"@rollup/rollup-win32-ia32-msvc": "4.52.4",
"@rollup/rollup-win32-x64-gnu": "4.52.4",
"@rollup/rollup-win32-x64-msvc": "4.52.4",
"@rollup/rollup-android-arm-eabi": "4.52.3",
"@rollup/rollup-android-arm64": "4.52.3",
"@rollup/rollup-darwin-arm64": "4.52.3",
"@rollup/rollup-darwin-x64": "4.52.3",
"@rollup/rollup-freebsd-arm64": "4.52.3",
"@rollup/rollup-freebsd-x64": "4.52.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
"@rollup/rollup-linux-arm-musleabihf": "4.52.3",
"@rollup/rollup-linux-arm64-gnu": "4.52.3",
"@rollup/rollup-linux-arm64-musl": "4.52.3",
"@rollup/rollup-linux-loong64-gnu": "4.52.3",
"@rollup/rollup-linux-ppc64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-musl": "4.52.3",
"@rollup/rollup-linux-s390x-gnu": "4.52.3",
"@rollup/rollup-linux-x64-gnu": "4.52.3",
"@rollup/rollup-linux-x64-musl": "4.52.3",
"@rollup/rollup-openharmony-arm64": "4.52.3",
"@rollup/rollup-win32-arm64-msvc": "4.52.3",
"@rollup/rollup-win32-ia32-msvc": "4.52.3",
"@rollup/rollup-win32-x64-gnu": "4.52.3",
"@rollup/rollup-win32-x64-msvc": "4.52.3",
"fsevents": "~2.3.2"
}
},
@@ -17909,15 +17906,15 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.46.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.0.tgz",
"integrity": "sha512-6+ZrB6y2bT2DX3K+Qd9vn7OFOJR+xSLDj+Aw/N3zBwUt27uTw2sw2TE2+UcY1RiyBZkaGbTkVg9SSdPNUG6aUw==",
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz",
"integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==",
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@typescript-eslint/typescript-estree": "8.46.0",
"@typescript-eslint/utils": "8.46.0"
"@typescript-eslint/eslint-plugin": "8.45.0",
"@typescript-eslint/parser": "8.45.0",
"@typescript-eslint/typescript-estree": "8.45.0",
"@typescript-eslint/utils": "8.45.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -19119,7 +19116,7 @@
"dependencies": {
"@goauthentik/prettier-config": "^3.1.0",
"@goauthentik/tsconfig": "^1.0.4",
"@types/node": "^24.7.0",
"@types/node": "^24.6.2",
"@types/semver": "^7.7.1",
"prettier": "^3.6.2",
"semver": "^7.7.2",
@@ -19204,7 +19201,7 @@
"@goauthentik/api": "^2025.10.0-rc1-1759234079",
"@goauthentik/core": "^1.0.0",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-node-resolve": "^16.0.2",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-swc": "^0.4.0",
"@swc/cli": "^0.7.8",
"@swc/core": "^1.13.19",
@@ -19214,7 +19211,7 @@
"formdata-polyfill": "^4.0.10",
"jquery": "^3.7.1",
"prettier": "^3.5.3",
"rollup": "^4.52.4",
"rollup": "^4.52.3",
"rollup-plugin-copy": "^3.5.0",
"weakmap-polyfill": "^2.0.4"
},

View File

@@ -89,7 +89,7 @@
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-python": "^6.2.1",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/legacy-modes": "^6.5.2",
"@codemirror/legacy-modes": "^6.5.1",
"@codemirror/theme-one-dark": "^6.1.3",
"@eslint/js": "^9.31.0",
"@floating-ui/dom": "^1.7.4",
@@ -116,7 +116,7 @@
"@patternfly/patternfly": "^4.224.2",
"@playwright/test": "^1.55.1",
"@sentry/browser": "^10.17.0",
"@spotlightjs/spotlight": "^4.1.3",
"@spotlightjs/spotlight": "^4.1.2",
"@storybook/addon-docs": "^9.1.10",
"@storybook/addon-links": "^9.1.10",
"@storybook/web-components": "^9.1.10",
@@ -125,7 +125,7 @@
"@types/grecaptcha": "^3.0.9",
"@types/guacamole-common-js": "^1.5.4",
"@types/mocha": "^10.0.10",
"@types/node": "^24.7.0",
"@types/node": "^24.6.2",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@typescript-eslint/eslint-plugin": "^8.38.0",
@@ -144,7 +144,7 @@
"dompurify": "^3.2.7",
"esbuild": "^0.25.10",
"esbuild-plugin-copy": "^2.1.1",
"eslint": "^9.37.0",
"eslint": "^9.36.0",
"eslint-plugin-lit": "^2.1.1",
"eslint-plugin-wc": "^3.0.2",
"fuse.js": "^7.1.0",
@@ -180,7 +180,7 @@
"turnstile-types": "^1.2.3",
"type-fest": "^5.0.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.46.0",
"typescript-eslint": "^8.45.0",
"unist-util-visit": "^5.0.0",
"vite": "^7.1.9",
"vitest": "^3.2.4",
@@ -192,10 +192,10 @@
"@esbuild/darwin-arm64": "^0.25.4",
"@esbuild/linux-arm64": "^0.25.4",
"@esbuild/linux-x64": "^0.25.4",
"@rollup/rollup-darwin-arm64": "^4.52.4",
"@rollup/rollup-linux-arm64-gnu": "^4.52.4",
"@rollup/rollup-linux-x64-gnu": "^4.52.4",
"chromedriver": "^141.0.0",
"@rollup/rollup-darwin-arm64": "^4.52.3",
"@rollup/rollup-linux-arm64-gnu": "^4.52.3",
"@rollup/rollup-linux-x64-gnu": "^4.52.3",
"chromedriver": "^140.0.4",
"p-iteration": "^1.1.8"
},
"wireit": {

View File

@@ -47,7 +47,7 @@
"dependencies": {
"@goauthentik/prettier-config": "^3.1.0",
"@goauthentik/tsconfig": "^1.0.4",
"@types/node": "^24.7.0",
"@types/node": "^24.6.2",
"@types/semver": "^7.7.1",
"prettier": "^3.6.2",
"semver": "^7.7.2",

View File

@@ -13,7 +13,7 @@
"@goauthentik/api": "^2025.10.0-rc1-1759234079",
"@goauthentik/core": "^1.0.0",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-node-resolve": "^16.0.2",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-swc": "^0.4.0",
"@swc/cli": "^0.7.8",
"@swc/core": "^1.13.19",
@@ -23,7 +23,7 @@
"formdata-polyfill": "^4.0.10",
"jquery": "^3.7.1",
"prettier": "^3.5.3",
"rollup": "^4.52.4",
"rollup": "^4.52.3",
"rollup-plugin-copy": "^3.5.0",
"weakmap-polyfill": "^2.0.4"
},

View File

@@ -84,10 +84,6 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
PFDrawer,
PFNav,
css`
.pf-c-page__main {
scrollbar-gutter: stable;
}
.pf-c-page__main,
.pf-c-drawer__content,
.pf-c-page__drawer {
@@ -220,15 +216,17 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
<div class="pf-c-drawer__main">
<div class="pf-c-drawer__content">
<div class="pf-c-drawer__body">
<ak-router-outlet
role="presentation"
class="pf-c-page__main"
tabindex="-1"
id="main-content"
defaultUrl="/administration/overview"
.routes=${ROUTES}
>
</ak-router-outlet>
<div class="pf-c-page__main">
<ak-router-outlet
role="presentation"
class="pf-c-page__main"
tabindex="-1"
id="main-content"
defaultUrl="/administration/overview"
.routes=${ROUTES}
>
</ak-router-outlet>
</div>
</div>
</div>
<ak-notification-drawer

View File

@@ -4,7 +4,6 @@ import "#elements/buttons/SpinnerButton/index";
import "#elements/events/LogViewer";
import "#elements/tasks/ScheduleList";
import "#elements/tasks/TaskList";
import "#elements/tasks/TaskOverview";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { AKElement } from "#elements/Base";
@@ -44,24 +43,6 @@ export class SystemTasksPage extends AKElement {
render(): TemplateResult {
return html` <main>
<ak-tabs>
<div
role="tabpanel"
tabindex="0"
slot="page-tasks"
id="page-tasks"
aria-label="${msg("Tasks")}"
>
<ak-task-overview></ak-task-overview>
<div
class="pf-l-grid pf-m-gutter pf-c-page__main-section pf-m-no-padding-mobile"
>
<div
class="pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-12-col-on-2xl"
>
<ak-task-list></ak-task-list>
</div>
</div>
</div>
<div
role="tabpanel"
tabindex="0"
@@ -78,6 +59,22 @@ export class SystemTasksPage extends AKElement {
</div>
</div>
</div>
<div
role="tabpanel"
tabindex="0"
slot="page-tasks"
id="page-tasks"
aria-label="${msg("Tasks")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-l-grid pf-m-gutter">
<div
class="pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-12-col-on-2xl"
>
<ak-task-list></ak-task-list>
</div>
</div>
</div>
</ak-tabs>
</main>`;
}

View File

@@ -107,7 +107,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
* @returns TemplateResult for status display
*/
private renderStatus(status: AdminStatus): SlottedTemplateResult {
return html`<div class="status-container">
return html` <div class="status-container">
<h2 class="status-heading">
<i class="${status.icon}" aria-hidden="true"></i>${this.renderValue()}
</h2>

View File

@@ -5,7 +5,7 @@ import { AdminStatus, AdminStatusCard } from "#admin/admin-overview/cards/AdminS
import { AdminApi, Version } from "@goauthentik/api";
import { msg, str } from "@lit/localize";
import { css, html, TemplateResult } from "lit";
import { html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
@customElement("ak-admin-status-version")
@@ -13,28 +13,6 @@ export class VersionStatusCard extends AdminStatusCard<Version> {
public override icon = "pf-icon pf-icon-bundle";
public override label = msg("Version");
static styles = [
...super.styles,
// HACK: Fixes Lit Analyzer's outdated parser.
(css as typeof css) /*css*/ `
.pf-c-card {
container-type: inline-size;
}
.pf-c-card__title {
@container (width < 200px) {
font-size: var(--pf-global--FontSize--sm);
}
}
.status-container {
@container (width < 200px) {
font-size: var(--pf-global--icon--FontSize--md);
}
}
`,
];
getPrimaryValue(): Promise<Version> {
return new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
}

View File

@@ -139,16 +139,6 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
}
renderSectionBefore(): TemplateResult {
const {
externalUsers = 0,
internalUsers = 0,
forecastedExternalUsers = 0,
forecastedInternalUsers = 0,
} = this.forecast || {};
const totalInternalUserEstimate = internalUsers + forecastedInternalUsers;
const totalExternalUserEstimate = externalUsers + forecastedExternalUsers;
return html`
<section class="pf-c-page__main-section pf-m-no-padding-bottom">
<div
@@ -156,40 +146,38 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
>
${this.renderGetLicenseCard()}
<ak-aggregate-card
role="status"
class="pf-l-grid__item"
icon="pf-icon pf-icon-user"
label=${msg("Forecast internal users")}
subtext=${msg(
str`Estimated user count one year from now based on ${internalUsers} current internal users and ${forecastedInternalUsers} forecasted internal users.`,
str`Estimated user count one year from now based on ${this.forecast?.internalUsers} current internal users and ${this.forecast?.forecastedInternalUsers} forecasted internal users.`,
)}
><span aria-label=${msg("Approximately")}>&#8776;</span
>${totalInternalUserEstimate}&nbsp;&nbsp;</ak-aggregate-card
>
~&nbsp;${(this.forecast?.internalUsers || 0) +
(this.forecast?.forecastedInternalUsers || 0)}
</ak-aggregate-card>
<ak-aggregate-card
role="status"
class="pf-l-grid__item"
icon="pf-icon pf-icon-user"
label=${msg("Forecast external users")}
subtext=${msg(
str`Estimated user count one year from now based on ${externalUsers} current external users and ${forecastedExternalUsers} forecasted external users.`,
str`Estimated user count one year from now based on ${this.forecast?.externalUsers} current external users and ${this.forecast?.forecastedExternalUsers} forecasted external users.`,
)}
><span aria-label=${msg("Approximately")}>&#8776;</span
>${totalExternalUserEstimate}&nbsp;&nbsp;</ak-aggregate-card
>
~&nbsp;${(this.forecast?.externalUsers || 0) +
(this.forecast?.forecastedExternalUsers || 0)}
</ak-aggregate-card>
<ak-aggregate-card
role="status"
class="pf-l-grid__item"
icon="pf-icon pf-icon-user"
label=${msg("Expiry")}
subtext=${msg("Cumulative license expiry")}
>${this.summary &&
>
${this.summary &&
this.summary?.status !== LicenseSummaryStatusEnum.Unlicensed
? Timestamp(this.summary.latestValid)
: html`<span aria-label=${msg("No expiry")}
>-</span
>`}</ak-aggregate-card
>
: "-"}
</ak-aggregate-card>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-bottom">

View File

@@ -19,7 +19,7 @@ import { DesignationToLabel } from "#admin/flows/utils";
import { Flow, FlowsApi, RbacPermissionsAssignedByUsersListModelEnum } from "@goauthentik/api";
import { msg, str } from "@lit/localize";
import { msg } from "@lit/localize";
import { css, CSSResult, html, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -85,7 +85,7 @@ export class FlowViewPage extends AKElement {
>
<div class="pf-l-grid pf-m-gutter">
<div
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-3-col-on-xl pf-m-3-col-on-2xl"
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-2-col-on-xl pf-m-2-col-on-2xl"
>
<div class="pf-c-card__title">${msg("Flow Info")}</div>
<div class="pf-c-card__body">
@@ -155,9 +155,6 @@ export class FlowViewPage extends AKElement {
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<button
aria-label=${msg(
str`Execute "${this.flow.name}" normally`,
)}
class="pf-c-button pf-m-block pf-m-primary"
@click=${() => {
const finalURL = `${
@@ -171,9 +168,6 @@ export class FlowViewPage extends AKElement {
${msg("Normal")}
</button>
<button
aria-label=${msg(
str`Execute "${this.flow.name}" as current user`,
)}
class="pf-c-button pf-m-block pf-m-secondary"
@click=${() => {
new FlowsApi(DEFAULT_CONFIG)
@@ -188,12 +182,9 @@ export class FlowViewPage extends AKElement {
});
}}
>
${msg("Current user")}
${msg("with current user")}
</button>
<button
aria-label=${msg(
str`Execute "${this.flow.name}" with inspector`,
)}
class="pf-c-button pf-m-block pf-m-secondary"
@click=${() => {
new FlowsApi(DEFAULT_CONFIG)
@@ -218,7 +209,7 @@ export class FlowViewPage extends AKElement {
});
}}
>
${msg("Use inspector")}
${msg("with inspector")}
</button>
</div>
</dd>
@@ -242,7 +233,7 @@ export class FlowViewPage extends AKElement {
</div>
</div>
<div
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-9-col-on-xl pf-m-9-col-on-2xl"
class="pf-c-card pf-l-grid__item pf-m-12-col pf-m-10-col-on-xl pf-m-10-col-on-2xl"
>
<div class="pf-c-card__title">${msg("Diagram")}</div>
<div class="pf-c-card__body">
@@ -314,14 +305,11 @@ export class FlowViewPage extends AKElement {
updated(changed: PropertyValues<this>) {
super.updated(changed);
if (changed.has("flow")) {
setPageDetails({
icon: "pf-icon pf-icon-process-automation",
header: this.flow?.name,
description: this.flow?.title,
});
}
setPageDetails({
icon: "pf-icon pf-icon-process-automation",
header: this.flow.name,
description: this.flow.title,
});
}
}

View File

@@ -31,10 +31,6 @@ export class MemberSelectTable extends TableModal<User> {
.show-disabled-toggle-group {
margin-inline-start: 0.5rem;
}
[part="toolbar"] {
gap: var(--pf-global--spacer--md);
}
`,
];
@@ -83,26 +79,27 @@ export class MemberSelectTable extends TableModal<User> {
this.fetch();
};
return html`<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group show-disabled-toggle-group">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.userListFilter === "all"}
@change=${toggleShowDisabledUsers}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
return html`&nbsp;
<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group show-disabled-toggle-group">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.userListFilter === "all"}
@change=${toggleShowDisabledUsers}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
</span>
<span class="pf-c-switch__label">${msg("Show inactive users")}</span>
</label>
<span class="pf-c-switch__label">${msg("Show inactive users")}</span>
</label>
</div>
</div>
</div>
</div>`;
</div>`;
}
row(item: User): SlottedTemplateResult[] {
@@ -125,8 +122,7 @@ export class MemberSelectTable extends TableModal<User> {
</div>
</div>
<div class="pf-c-modal-box__body pf-m-light">${this.renderTable()}</div>
<fieldset class="pf-c-modal-box__footer">
<legend class="sr-only">${msg("Form actions")}</legend>
<fieldset name="actions" class="pf-c-modal-box__footer">
<ak-spinner-button
.callAction=${() => {
return this.confirm(this.selectedElements).then(() => {

View File

@@ -209,8 +209,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
html`<ak-empty-state icon="pf-icon-module"
><span>${msg("No Policies bound.")}</span>
<div slot="body">${msg("No policies are currently bound to this object.")}</div>
<fieldset class="pf-c-form__group pf-m-action" slot="primary">
<legend class="sr-only">${msg("Policy actions")}</legend>
<div slot="primary">
<ak-policy-wizard
createText=${msg("Create and bind Policy")}
showBindingPage
@@ -230,7 +229,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
${msg("Bind existing policy/group/user")}
</button>
</ak-forms-modal>
</fieldset>
</div>
</ak-empty-state>`,
);
}

View File

@@ -128,33 +128,34 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
}
renderToolbarAfter(): TemplateResult {
return html`<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.hideManaged}
@change=${() => {
this.hideManaged = !this.hideManaged;
this.page = 1;
this.fetch();
updateURLParams({
hideManaged: this.hideManaged,
});
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
return html`&nbsp;
<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.hideManaged}
@change=${() => {
this.hideManaged = !this.hideManaged;
this.page = 1;
this.fetch();
updateURLParams({
hideManaged: this.hideManaged,
});
}}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
</span>
<span class="pf-c-switch__label">${msg("Hide managed mappings")}</span>
</label>
<span class="pf-c-switch__label">${msg("Hide managed mappings")}</span>
</label>
</div>
</div>
</div>
</div>`;
</div>`;
}
}

View File

@@ -82,7 +82,6 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
renderExampleLDAP(): TemplateResult {
return html`
<button
type="button"
class="pf-c-button pf-m-secondary"
role="button"
@click=${() => {
@@ -106,7 +105,6 @@ export class PolicyTestForm extends Form<PropertyMappingTestRequest> {
${msg("Active Directory User")}
</button>
<button
type="button"
class="pf-c-button pf-m-secondary"
role="button"
@click=${() => {

View File

@@ -54,9 +54,10 @@ export function renderForm({ provider = {}, errors = {}, brand }: LDAPProviderFo
name="name"
placeholder=${msg("Provider name...")}
value=${ifDefined(provider.name)}
label=${msg("Name")}
label=${msg("Provider Name")}
.errorMessages=${errors.name}
required
help=${msg("Method's display Name.")}
></ak-text-input>
<ak-radio-input
label=${msg("Bind mode")}

View File

@@ -12,7 +12,7 @@ import { ConnectionToken, RacApi, RACProvider } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { CSSResult, html, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { customElement, property } from "lit/decorators.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
@@ -77,7 +77,6 @@ export class ConnectionTokenListPage extends Table<ConnectionToken> {
return item.providerObj.name ?? null;
}
@state()
protected get columns(): TableColumn[] {
if (this.provider) {
return [

View File

@@ -110,6 +110,7 @@ export function renderForm({ provider = {}, errors = {}, update }: SCIMProviderF
label=${msg("Name")}
.errorMessages=${errors.name}
required
help=${msg("Method's display Name.")}
></ak-text-input>
<ak-form-group open label="${msg("Protocol settings")}">
<div class="pf-c-form">

View File

@@ -34,12 +34,16 @@ export class ObjectPermissionPage extends AKElement {
render() {
return html` <ak-tabs pageIdentifier="permissionPage" ?vertical=${!this.embedded}>
${this.model === RbacPermissionsAssignedByUsersListModelEnum.AuthentikCoreUser
? this.renderCoreUser()
: nothing}
${this.model === RbacPermissionsAssignedByUsersListModelEnum.AuthentikRbacRole
? this.renderRbacRole()
: nothing}
${
this.model === RbacPermissionsAssignedByUsersListModelEnum.AuthentikCoreUser
? this.renderCoreUser()
: nothing
}
${
this.model === RbacPermissionsAssignedByUsersListModelEnum.AuthentikRbacRole
? this.renderRbacRole()
: nothing
}
<div
role="tabpanel"
tabindex="0"
@@ -88,7 +92,8 @@ export class ObjectPermissionPage extends AKElement {
</div>
</div>
</div>
</ak-tabs>`;
</ak-tabs>
</main>`;
}
renderCoreUser() {
@@ -99,20 +104,21 @@ export class ObjectPermissionPage extends AKElement {
slot="page-assigned-global-permissions"
id="page-assigned-global-permissions"
aria-label="${msg("Assigned global permissions")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Assigned global permissions")}</div>
<div class="pf-c-card__body">
${msg(
"Permissions assigned to this user which affect all object instances of a given type.",
)}
</div>
<div class="pf-c-card__body">
<ak-user-assigned-global-permissions-table
userId=${this.objectPk as number}
>
</ak-user-assigned-global-permissions-table>
<div class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Assigned global permissions")}</div>
<div class="pf-c-card__body">
${msg(
"Permissions assigned to this user which affect all object instances of a given type.",
)}
</div>
<div class="pf-c-card__body">
<ak-user-assigned-global-permissions-table
userId=${this.objectPk as number}
>
</ak-user-assigned-global-permissions-table>
</div>
</div>
</div>
</div>
@@ -122,20 +128,21 @@ export class ObjectPermissionPage extends AKElement {
slot="page-assigned-object-permissions"
id="page-assigned-object-permissions"
aria-label="${msg("Assigned object permissions")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Assigned object permissions")}</div>
<div class="pf-c-card__body">
${msg(
"Permissions assigned to this user affecting specific object instances.",
)}
</div>
<div class="pf-c-card__body">
<ak-user-assigned-object-permissions-table
userId=${this.objectPk as number}
>
</ak-user-assigned-object-permissions-table>
<div class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Assigned object permissions")}</div>
<div class="pf-c-card__body">
${msg(
"Permissions assigned to this user affecting specific object instances.",
)}
</div>
<div class="pf-c-card__body">
<ak-user-assigned-object-permissions-table
userId=${this.objectPk as number}
>
</ak-user-assigned-object-permissions-table>
</div>
</div>
</div>
</div>
@@ -150,20 +157,21 @@ export class ObjectPermissionPage extends AKElement {
slot="page-assigned-global-permissions"
id="page-assigned-global-permissions"
aria-label="${msg("Assigned global permissions")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Assigned global permissions")}</div>
<div class="pf-c-card__body">
${msg(
"Permissions assigned to this role which affect all object instances of a given type.",
)}
</div>
<div class="pf-c-card__body">
<ak-role-assigned-global-permissions-table
roleUuid=${this.objectPk as string}
>
</ak-role-assigned-global-permissions-table>
<div class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Assigned global permissions")}</div>
<div class="pf-c-card__body">
${msg(
"Permissions assigned to this role which affect all object instances of a given type.",
)}
</div>
<div class="pf-c-card__body">
<ak-role-assigned-global-permissions-table
roleUuid=${this.objectPk as string}
>
</ak-role-assigned-global-permissions-table>
</div>
</div>
</div>
</div>
@@ -173,20 +181,21 @@ export class ObjectPermissionPage extends AKElement {
slot="page-assigned-object-permissions"
id="page-assigned-object-permissions"
aria-label="${msg("Assigned object permissions")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Assigned object permissions")}</div>
<div class="pf-c-card__body">
${msg(
"Permissions assigned to this user affecting specific object instances.",
)}
</div>
<div class="pf-c-card__body">
<ak-role-assigned-object-permissions-table
roleUuid=${this.objectPk as string}
>
</ak-role-assigned-object-permissions-table>
<div class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-card__title">${msg("Assigned object permissions")}</div>
<div class="pf-c-card__body">
${msg(
"Permissions assigned to this user affecting specific object instances.",
)}
</div>
<div class="pf-c-card__body">
<ak-role-assigned-object-permissions-table
roleUuid=${this.objectPk as string}
>
</ak-role-assigned-object-permissions-table>
</div>
</div>
</div>
</div>

View File

@@ -51,11 +51,9 @@ export class RoleAssignedObjectPermissionTable extends Table<RoleAssignedObjectP
return value.codename !== `add_${this.model?.split(".")[1]}`;
});
this.modelPermissions = modelPermissions;
this.requestUpdate("columns");
return perms;
}
@state()
protected get columns(): TableColumn[] {
const permissions = this.modelPermissions?.results ?? [];

View File

@@ -51,13 +51,9 @@ export class UserAssignedObjectPermissionTable extends Table<UserAssignedObjectP
return value.codename !== `add_${this.model?.split(".")[1]}`;
});
this.modelPermissions = modelPermissions;
this.requestUpdate("columns");
return perms;
}
@state()
protected get columns(): TableColumn[] {
const permissions = this.modelPermissions?.results ?? [];

View File

@@ -210,7 +210,7 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
}
renderToolbarAfter(): TemplateResult {
return html`<div class="pf-c-toolbar__group pf-m-filter-group">
return html` <div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
<label

View File

@@ -1,16 +1,10 @@
import {
Config,
ConfigFromJSON,
CurrentBrand,
CurrentBrandFromJSON,
FlowLayoutEnum,
} from "@goauthentik/api";
import { Config, ConfigFromJSON, CurrentBrand, CurrentBrandFromJSON } from "@goauthentik/api";
export interface GlobalAuthentik {
_converted?: boolean;
locale?: string;
flow?: {
layout: FlowLayoutEnum;
layout: string;
};
config: Config;
brand: CurrentBrand;

View File

@@ -1,14 +1,6 @@
import {
Device,
DeviceChallenge,
DeviceClassesEnum,
EventActions,
IntentEnum,
SeverityEnum,
UserTypeEnum,
} from "@goauthentik/api";
import { Device, EventActions, IntentEnum, SeverityEnum, UserTypeEnum } from "@goauthentik/api";
import { msg, str } from "@lit/localize";
import { msg } from "@lit/localize";
/* Various tables in the API for which we need to supply labels */
@@ -75,10 +67,7 @@ export function severityToLevel(severity?: SeverityEnum | null): string {
return "pf-m-info";
}
/**
* @todo Add verbose_name field to now vendored OTP devices
* @todo We seem to have these constants in the `ModelEnum` object in lowercase.
*/
// TODO: Add verbose_name field to now vendored OTP devices
export const deviceTypeToLabel = new Map<string, string>([
["authentik_stages_authenticator_static.StaticDevice", msg("Static tokens")],
["authentik_stages_authenticator_totp.TOTPDevice", msg("TOTP Device")],
@@ -87,26 +76,6 @@ export const deviceTypeToLabel = new Map<string, string>([
export const deviceTypeName = (device: Device) =>
deviceTypeToLabel.get(device.type) ?? device?.verboseName ?? "";
export function formatDeviceChallengeMessage(deviceChallenge?: DeviceChallenge | null): string {
switch (deviceChallenge?.deviceClass) {
case DeviceClassesEnum.Email: {
const { email } = deviceChallenge.challenge;
return email
? msg(str`A code has been sent to your address: ${email}`)
: msg("A code has been sent to your email address.");
}
case DeviceClassesEnum.Sms:
return msg("A one-time use code has been sent to you via SMS text message.");
case DeviceClassesEnum.Totp:
return msg("Open your authenticator app to retrieve a one-time use code.");
case DeviceClassesEnum.Static:
return msg("Enter a one-time recovery code for this user.");
}
return msg("Enter the code from your authenticator device.");
}
const _userTypeToLabel = new Map<UserTypeEnum | undefined, string>([
[UserTypeEnum.Internal, msg("Internal")],
[UserTypeEnum.External, msg("External")],

View File

@@ -21,9 +21,6 @@
--ak-dark-background-light-ish: #212427;
--ak-dark-background-lighter: #2b2e33;
--ak-flow-background-color-contrast: var(--pf-global--Color--100);
--ak-flow-footer-color: var(--pf-global--Color--light-100);
/* PatternFly likes to override global variables for some reason */
--ak-global--Color--100: var(--pf-global--Color--100);
@@ -42,96 +39,6 @@
--pf-global--disabled-color--300: color-mix(in srgb, GrayText 100%, CanvasText 100%);
}
/* #endregion */
/* #region Scrollbars */
/**
* Scrollbar colors derived from default user agent styles.
*
* @remarks
*
* Scrollbar colors may be ignored by the browser depending on a few factors:
*
* - A preference for increased contrast
* - A preference for scrollbar visibility
* - The pointer precision available
* - Operating system differences
*/
:root {
--ak-scrollbar-background-color: hsl(0 0% 98%);
--ak-scrollbar-thumb-background-color: hsl(0 0% 76%);
}
/* Applicable to browsers with a WebKit lineage (Chrome, Edge, Safari) */
::-webkit-scrollbar {
background: var(--ak-scrollbar-background-color);
/* Emulate thin scrollbar sizing. */
@media (pointer: fine) {
max-height: 12px;
max-width: 12px;
}
}
/* Necessary to avoid background color mismatch. */
::-webkit-scrollbar-track {
background: var(--ak-scrollbar-background-color);
}
::-webkit-scrollbar-thumb {
background: var(--ak-scrollbar-thumb-background-color);
/* Applies consistent shape across browsers and platforms */
border: 3px solid transparent;
border-radius: 8px;
background-clip: content-box;
/* Emulate thin scrollbar sizing. */
@media (pointer: fine) {
border: 3px solid transparent;
border-radius: 8px;
}
}
/**
* Hides arrow buttons on the scrollbar.
* Not applicable to Edge when scrollbars are set to always visible.
*/
::-webkit-scrollbar-button {
display: none;
height: 0;
width: 0;
}
@supports (scrollbar-color: auto) {
:root {
/**
* Applies consistent colors across browsers and platforms.
* Not applicable when thin scrollbars are preferred.
*/
scrollbar-color: var(--ak-scrollbar-thumb-background-color)
var(--ak-scrollbar-background-color);
}
}
@supports (scrollbar-width: thin) {
.pf-c-page__main,
.pf-c-nav__list,
.pf-c-card__body {
scrollbar-width: thin;
@media (pointer: coarse) {
/**
* Avoids issues on touch devices where thin scrollbars
* are difficult to interact with.
*/
scrollbar-width: auto;
}
}
}
/* #endregion */
.pf-c-form__group {
column-gap: var(--pf-global--spacer--md);
}
@@ -156,6 +63,29 @@
color: var(--pf-c-form__label-required--Color);
}
@supports selector(::-webkit-scrollbar) {
::-webkit-scrollbar {
width: 5px;
height: 5px;
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background-color: var(--ak-accent);
}
::-webkit-scrollbar-track {
background-color: transparent;
}
::-webkit-scrollbar-corner {
background-color: transparent;
}
}
@supports not selector(::-webkit-scrollbar) {
:root {
scrollbar-color: var(--ak-accent) transparent;
}
}
html {
--pf-c-nav__link--PaddingTop: 0.5rem;
--pf-c-nav__link--PaddingRight: 0.5rem;
@@ -231,10 +161,8 @@ html > form > input {
/* #region Tabs */
.pf-c-tabs {
--pf-c-tabs--inset: var(--pf-global--spacer--lg);
--pf-c-tabs--m-vertical--m-box--inset: var(--pf-global--spacer--lg);
margin-block-start: var(--pf-global--spacer--xs);
background-color: transparent;
&.pf-m-box.pf-m-vertical {
@@ -314,38 +242,27 @@ ak-tabs[vertical] {
display: block;
position: relative;
width: 100%;
flex: 1 1 auto;
place-content: center;
}
@media (max-width: 1199px) {
.pf-c-login__container {
display: flex;
flex-direction: column;
}
}
.ak-login-container {
max-width: 35rem;
width: 100%;
height: calc(100vh - var(--pf-global--spacer--lg) - var(--pf-global--spacer--lg));
width: 35rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.pf-c-login__header {
flex-grow: 1;
}
.pf-c-login__footer {
color: var(--ak-flow-footer-color);
flex: 0 0 auto;
flex-grow: 2;
display: flex;
justify-content: end;
flex-direction: column;
}
@media (max-width: 768px) {
:root {
--ak-flow-footer-color: var(--ak-flow-background-color-contrast);
}
}
.pf-c-login__footer ul.pf-c-list.pf-m-inline {
justify-content: center;
padding: 2rem 0;
@@ -386,95 +303,12 @@ ak-tabs[vertical] {
/* #region Fields */
fieldset {
--ak-fieldset-border-width: thin;
--ak-fieldset-border-color: var(--pf-global--BackgroundColor--light-100);
--ak-legend-margin-inline-base: var(--pf-global--spacer--sm);
--ak-legend-padding-inline-base: var(--pf-global--spacer--sm);
border-color: var(--ak-fieldset-border-color);
border-width: var(--ak-fieldset-border-width);
padding: var(--ak-legend-padding-inline-base) !important;
@media (prefers-contrast: more) {
--ak-fieldset-border-color: var(--pf-global--BorderColor--200);
}
@media (prefers-contrast: less) {
--ak-fieldset-border-color: transparent;
}
& > legend {
line-height: 1;
padding: var(--ak-legend-padding-inline-base) !important;
margin-inline-start: var(
--ak-legend-margin-inline-start,
var(--ak-legend-margin-inline-base)
) !important;
margin-inline-end: var(
--ak-legend-margin-inline-end,
var(--ak-legend-margin-inline-base)
) !important;
}
&:has(legend.sr-only) {
border-width: 0;
}
&.pf-c-card {
--ak-legend-margin-inline-start: calc(
var(--pf-c-card--child--PaddingLeft) - var(--ak-legend-padding-inline-base)
);
--ak-legend-margin-inline-end: calc(
var(--pf-c-card--child--PaddingRight) - var(--ak-legend-padding-inline-base)
);
--pf-c-card--BoxShadow: none;
--pf-c-card--BackgroundColor: transparent;
}
&.pf-c-card,
&.pf-c-form__group {
border-radius: var(--pf-global--BorderRadius--sm);
}
&.pf-c-form__group {
display: flex;
flex-wrap: wrap;
&.pf-m-action {
gap: var(--pf-global--spacer--md) var(--pf-global--spacer--sm);
margin-block-start: 0;
margin-block-end: calc(var(--pf-c-form__group--m-action--MarginTop) / 2);
}
}
&.pf-c-modal-box__footer {
--ak-legend-padding-inline-base: var(--pf-global--spacer--md);
padding-block: calc(var(--ak-legend-padding-inline-base) / 2);
border-inline: none;
border-block-end: none;
--pf-c-modal-box__footer--c-button--sm--MarginRight: var(
--pf-c-modal-box__footer--c-button--MarginRight
);
& > ak-spinner-button:not(:last-child) {
margin-right: var(--pf-c-modal-box__footer--c-button--MarginRight);
}
}
fieldset[name="actions"] {
border: none;
display: flex;
gap: var(--pf-global--spacer--sm);
}
/* #endregion */
/* #region Notifications */
.pf-c-notification-drawer {
--pf-c-notification-drawer--BackgroundColor: var(--pf-global--BackgroundColor--150);
}
/* #endregion */
/* #region Switch */
.pf-c-switch {
--pf-c-switch__input--focus__toggle--OutlineWidth: 0;
@@ -553,16 +387,11 @@ fieldset {
}
@media (min-height: 60rem) {
.pf-c-login[data-layout="stacked"] .pf-c-login__main {
.pf-c-login.stacked .pf-c-login__main {
margin-top: 13rem;
}
}
.pf-c-login[data-layout="sidebar_left"],
.pf-c-login[data-layout="sidebar_right"] {
--ak-flow-footer-color: var(--ak-flow-background-color-contrast);
}
.pf-c-data-list {
padding-inline-start: 0;
}

View File

@@ -23,18 +23,6 @@ body {
color-scheme: dark;
}
/* #region Scrollbars */
:root {
/**
* Scrollbar colors derived from default user agent styles.
*/
--ak-scrollbar-background-color: hsl(0 0% 18%);
--ak-scrollbar-thumb-background-color: hsl(0 0% 42%);
}
/* #endregion */
.pf-c-radio {
--pf-c-radio__label--Color: var(--ak-dark-foreground);
}
@@ -91,22 +79,6 @@ body {
/* #endregion */
/* #region Fields */
fieldset {
--ak-fieldset-border-color: var(--pf-global--BackgroundColor--dark-transparent-200);
@media (prefers-contrast: more) {
--ak-fieldset-border-color: var(--pf-global--BorderColor--300);
}
@media (prefers-contrast: less) {
--ak-fieldset-border-color: transparent;
}
}
/* #endregion */
.pf-c-toolbar {
--pf-c-toolbar--BackgroundColor: var(--ak-dark-background-light);
}
@@ -205,17 +177,15 @@ select.pf-c-form-control {
}
.pf-c-form-control {
/* !important is required to ensure priority when in compatibility mode. */
--pf-c-form-control--BorderTopColor: transparent !important;
--pf-c-form-control--BorderRightColor: transparent !important;
--pf-c-form-control--BorderLeftColor: transparent !important;
--pf-global--BackgroundColor--100: var(--ak-dark-background-light) !important;
--pf-c-form-control--BackgroundColor: var(--ak-dark-background-light) !important;
--pf-c-form-control--Color: var(--ak-dark-foreground) !important;
--pf-c-form-control--BackgroundColor: var(--ak-dark-background-light);
--pf-c-form-control--Color: var(--ak-dark-foreground);
--pf-c-form-control--readonly--BackgroundColor: var(--ak-dark-background-light) !important;
--pf-c-form-control--disabled--BackgroundColor: var(--ak-dark-background-light) !important;
--pf-c-form-control--readonly--BackgroundColor: var(--ak-dark-background-light);
--pf-c-form-control--disabled--BackgroundColor: var(--ak-dark-background-light);
}
/* #endregion */
@@ -394,13 +364,11 @@ select.pf-c-form-control {
}
.pf-c-notification-drawer {
--pf-c-notification-drawer--BackgroundColor: var(--ak-dark-background) !important;
--pf-c-notification-drawer__header--BackgroundColor: var(
--ak-dark-background-lighter
) !important;
--pf-c-notification-drawer--BackgroundColor: var(--ak-dark-background);
}
.pf-c-notification-drawer__header {
background-color: var(--ak-dark-background-lighter);
color: var(--ak-dark-foreground);
}

View File

@@ -66,13 +66,8 @@ export class AppIcon extends AKElement implements IAppIcon {
padding: var(--icon-border);
max-height: calc(var(--icon-height) + var(--icon-border) + var(--icon-border));
line-height: calc(var(--icon-height) + var(--icon-border) + var(--icon-border));
filter: drop-shadow(hsl(0deg 0% 85%) 5px 5px 5px);
filter: drop-shadow(5px 5px 5px rgba(128, 128, 128, 0.25));
}
:host([theme="dark"]) .icon {
filter: drop-shadow(hsl(0deg 0% 11%) 5px 5px 4px);
}
div {
height: calc(var(--icon-height) + var(--icon-border) + var(--icon-border));
}
@@ -82,14 +77,14 @@ export class AppIcon extends AKElement implements IAppIcon {
render(): TemplateResult {
// prettier-ignore
return match([this.name, this.icon])
.with([P.nullish, P.nullish],
() => html`<div><i aria-hidden="true" class="icon fas fa-question-circle"></i></div>`)
.with([undefined, undefined],
() => html`<div><i class="icon fas fa-question-circle" aria-hidden="true"></i></div>`)
.with([P._, P.string.startsWith("fa://")],
([_name, icon]) => html`<div><i aria-hidden="true" class="icon fas ${icon.replaceAll("fa://", "")}"></i></div>`)
([_name, icon]) => html`<div><i class="icon fas ${icon.replaceAll("fa://", "")}"></i></div>`)
.with([P._, P.string],
([_name, icon]) => html`<img aria-hidden="true" class="icon pf-c-avatar" src="${icon}" alt="${msg("Application Icon")}" />`)
.with([P.string, P.nullish],
([name]) => html`<span aria-hidden="true" class="icon">${name.charAt(0).toUpperCase()}</span>`)
([_name, icon]) => html`<img class="icon pf-c-avatar" src="${icon}" alt="${msg("Application Icon")}" />`)
.with([P.string, undefined],
([name]) => html`<span class="icon">${name.charAt(0).toUpperCase()}</span>`)
.exhaustive();
}
}

View File

@@ -19,24 +19,20 @@ export interface IExpand {
@customElement("ak-expand")
export class Expand extends AKElement implements IExpand {
@property({ type: Boolean })
public expanded = false;
expanded = false;
@property({ type: String, attribute: "text-open" })
public textOpen = msg("Show less");
textOpen = msg("Show less");
@property({ type: String, attribute: "text-closed" })
public textClosed = msg("Show more");
textClosed = msg("Show more");
static styles = [
PFBase,
PFExpandableSection,
css`
.pf-c-expandable-section {
display: grid;
grid-template-columns: 1fr;
}
.pf-c-expandable-section__toggle {
user-select: none;
.pf-c-expandable-section.pf-m-display-lg {
background-color: var(--pf-global--BackgroundColor--100);
}
`,
];
@@ -50,8 +46,7 @@ export class Expand extends AKElement implements IExpand {
<button
type="button"
class="pf-c-expandable-section__toggle"
aria-expanded=${this.expanded ? "true" : "false"}
aria-controls="expandable-content"
aria-expanded="${this.expanded}"
@click=${() => {
this.expanded = !this.expanded;
}}
@@ -63,11 +58,7 @@ export class Expand extends AKElement implements IExpand {
>${this.expanded ? this.textOpen : this.textClosed}</span
>
</button>
<div
id="expandable-content"
class="pf-c-expandable-section__content"
?hidden=${!this.expanded}
>
<div class="pf-c-expandable-section__content" ?hidden=${!this.expanded}>
<slot></slot>
</div>
</div>`;

View File

@@ -64,6 +64,10 @@ export class AggregateCard extends AKElement implements IAggregateCard {
PFCard,
PFFlex,
css`
.pf-c-card {
container-type: inline-size;
}
.pf-c-card.pf-c-card-aggregate {
height: 100%;
}
@@ -75,6 +79,10 @@ export class AggregateCard extends AKElement implements IAggregateCard {
align-items: center;
gap: var(--pf-global--spacer--sm);
flex: 1 1 auto;
@container (width < 200px) {
font-size: var(--pf-global--FontSize--sm);
}
}
.subtext {
@@ -86,11 +94,14 @@ export class AggregateCard extends AKElement implements IAggregateCard {
padding-left: calc(var(--pf-c-card--child--PaddingLeft) / 2);
padding-right: calc(var(--pf-c-card--child--PaddingRight) / 2);
}
.status-container {
font-size: var(--pf-global--icon--FontSize--lg);
text-align: center;
@container (width < 200px) {
font-size: var(--pf-global--icon--FontSize--md);
}
.status-heading {
display: flex;
gap: var(--pf-global--spacer--sm);
@@ -108,20 +119,10 @@ export class AggregateCard extends AKElement implements IAggregateCard {
.pf-c-card__footer {
min-height: 1ex;
}
:host([role="status"]) {
text-align: center;
}
`,
];
renderInner(): SlottedTemplateResult {
if (this.role === "status") {
return html`<div class="status-container">
<slot class="status-heading"></slot>
</div>`;
}
return html`<slot></slot>`;
}

View File

@@ -1,95 +0,0 @@
/**
* @file Intersection Observer Decorator for LitElement
*/
import { LitElement } from "lit";
import { property } from "lit/decorators.js";
/**
* Type for the decorator
*/
export type IntersectionDecorator = <T extends LitElement>(target: T, propertyKey: keyof T) => void;
/**
* A decorator that applies an IntersectionObserver to the element.
* This is useful for lazy-loading elements that are not visible on the screen.
*
* @param init Configuration options for the IntersectionObserver
*
* ```ts
* class MyElement extends LitElement {
* \@intersectionObserver()
* protected visible!: boolean;
*
* \@intersectionObserver({ threshold: 0.5, rootMargin: '50px' })
* protected halfVisible!: boolean;
*
* render() {
* if (!this.visible) return nothing;
*
* return html`
* <div>
* Content is visible!
* ${this.halfVisible ? html`<p>More than 50% visible</p>` : ''}
* </div>
* `;
* }
* }
* ```
*/
export function intersectionObserver(init: IntersectionObserverInit = {}): IntersectionDecorator {
return <T extends LitElement, K extends keyof T>(target: T, key: K) => {
//#region Prepare observer
property({ attribute: false, useDefault: false })(target, key);
const observerCallback: IntersectionObserverCallback = (entries) => {
for (const entry of entries) {
const currentTarget = entry.target as T;
const cachedIntersecting = currentTarget[key];
if (cachedIntersecting !== entry.isIntersecting) {
Object.assign(currentTarget, {
[key]: entry.isIntersecting,
});
currentTarget.requestUpdate(key, cachedIntersecting);
}
}
};
//#endregion
//#region Lifecycle
const observer = new IntersectionObserver(observerCallback, {
root: null,
rootMargin: "0px",
threshold: 0,
...init,
});
const { connectedCallback, disconnectedCallback } = target;
target.connectedCallback = function (this: T) {
connectedCallback?.call(this);
if (this.hasUpdated) {
observer.observe(this);
} else {
this.updateComplete.then(() => {
observer.observe(this);
});
}
};
target.disconnectedCallback = function (this: LitElement) {
disconnectedCallback?.call(this);
if (observer) {
observer.disconnect();
}
};
//#endregion
};
}

View File

@@ -58,7 +58,6 @@ export class DeleteObjectsTable<T extends object> extends Table<T> {
return name || null;
}
@state()
protected get columns(): TableColumn[] {
return this.metadata(this.objects[0]).map((element) => [element.key]);
}

View File

@@ -92,14 +92,6 @@ export class ModalForm extends ModalButton {
}
};
#refreshListener = (e: Event): void => {
// if the modal should stay open after successful submit, prevent EVENT_REFRESH from bubbling
// to the parent components (which would cause table refreshes that destroy the modal)
if (!this.closeAfterSuccessfulSubmit) {
e.stopPropagation();
}
};
#scrollListener = () => {
window.dispatchEvent(
new CustomEvent("scroll", {
@@ -108,16 +100,6 @@ export class ModalForm extends ModalButton {
);
};
override connectedCallback(): void {
super.connectedCallback();
this.addEventListener(EVENT_REFRESH, this.#refreshListener);
}
override disconnectedCallback(): void {
super.disconnectedCallback();
this.removeEventListener(EVENT_REFRESH, this.#refreshListener);
}
protected renderModalInner(): TemplateResult {
return html`${this.loading
? html`<ak-loading-overlay topmost></ak-loading-overlay>`
@@ -133,7 +115,7 @@ export class ModalForm extends ModalButton {
<section class="pf-c-modal-box__body" @scroll=${this.#scrollListener}>
<slot name="form"></slot>
</section>
<fieldset class="pf-c-modal-box__footer">
<fieldset name="actions" class="pf-c-modal-box__footer">
<legend class="sr-only">${msg("Form actions")}</legend>
${this.showSubmitButton
? html`<button

View File

@@ -22,8 +22,6 @@ import { ifPresent } from "#elements/utils/attributes";
import { Pagination } from "@goauthentik/api";
import { kebabCase } from "change-case";
import { msg, str } from "@lit/localize";
import { css, CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { property, state } from "lit/decorators.js";
@@ -84,12 +82,6 @@ export abstract class Table<T extends object>
container-type: inline-size;
}
[part="table-container"] {
@media (max-width: 1199px) {
overflow-x: auto;
}
}
.pf-c-table {
.presentational {
--pf-c-table--cell--MinWidth: 0;
@@ -106,10 +98,10 @@ export abstract class Table<T extends object>
td:has(ak-action-button),
td:has(ak-forms-modal),
td:has(ak-rbac-object-permission-modal) {
& > .pf-c-button,
& > button[slot="trigger"]:has(i),
& > *::part(spinner-button),
& > *::part(button) {
.pf-c-button,
button[slot="trigger"]:has(i),
*::part(spinner-button),
ak-rbac-object-permission-modal::part(button) {
--pf-global--spacer--form-element: 0;
padding-inline: 0.5em !important;
@@ -137,7 +129,6 @@ export abstract class Table<T extends object>
.pf-m-search-filter {
flex: 1 1 auto;
margin-inline: 0;
}
.pf-c-table thead .pf-c-table__check {
min-width: 3rem;
@@ -146,27 +137,16 @@ export abstract class Table<T extends object>
margin-top: calc(var(--pf-c-table__check--input--MarginTop) + 1px);
}
.pf-c-toolbar {
display: flex;
flex-flow: row wrap;
padding-inline: var(--pf-global--spacer--md);
gap: var(--pf-global--spacer--sm);
}
.pf-c-toolbar__content {
flex: 1 1 auto;
flex-flow: row wrap;
margin: 0;
justify-content: space-between;
gap: var(--pf-global--spacer--sm);
padding-inline: 0;
.pf-c-switch {
--pf-c-switch--ColumnGap: var(--pf-c-toolbar__item--m-search-filter--spacer);
}
}
.pf-c-toolbar__group {
flex-flow: row wrap;
gap: var(--pf-global--spacer--sm);
.pf-c-card__title .pf-icon {
@@ -174,10 +154,6 @@ export abstract class Table<T extends object>
}
}
[part="toolbar-primary"] {
flex: 2 1 auto;
}
.pf-c-table {
--pf-c-table--m-striped__tr--BackgroundColor: var(
--pf-global--BackgroundColor--dark-300
@@ -242,24 +218,13 @@ export abstract class Table<T extends object>
*/
#columnCount = 0;
#columnIDs = new WeakMap<TableColumn, string>();
#synchronizeColumnProperties() {
#synchronizeColumnCount() {
let nextColumnCount = this.columns.length;
if (this.checkbox) nextColumnCount += 1;
if (this.expandable) nextColumnCount += 1;
this.#columnCount = nextColumnCount;
for (const column of this.columns) {
const [label] = column;
if (!label) continue;
const columnName = kebabCase(label);
this.#columnIDs.set(column, columnName);
}
}
@state()
@@ -412,7 +377,7 @@ export abstract class Table<T extends object>
changedProperties.has("checkbox") ||
changedProperties.has("expandable")
) {
this.#synchronizeColumnProperties();
this.#synchronizeColumnCount();
}
}
@@ -739,15 +704,18 @@ export abstract class Table<T extends object>
${this.checkbox ? renderCheckbox() : nothing}
${this.expandable ? renderExpansion() : nothing}
${this.row(item).map((cell, columnIndex) => {
const columnID = this.#columnIDs.get(this.columns[columnIndex]);
const [columnLabel] = this.columns[columnIndex];
const columnHeaderID = `table-header-${columnIndex}`;
const headers = groupHeaderID
? `${groupHeaderID} ${columnID}`.trim()
: columnID;
? `${groupHeaderID} ${columnHeaderID}`
: columnHeaderID;
return html`<td
class=${ifPresent(!columnID, "presentational")}
headers=${ifPresent(headers)}
class=${classMap({
presentational: !columnLabel,
})}
headers=${headers}
>
${cell}
</td>`;
@@ -795,25 +763,18 @@ export abstract class Table<T extends object>
if (this.renderToolbarAfter) {
primaryToolbar.push(
html`<div class="pf-c-toolbar__group" part="toolbar-after">
${this.renderToolbarAfter()}
</div>`,
html`<div class="pf-c-toolbar__group">${this.renderToolbarAfter()}</div>`,
);
}
return html`<header
class="pf-c-toolbar"
role="toolbar"
aria-label="${label}"
part="toolbar"
>
return html`<header class="pf-c-toolbar" role="toolbar" aria-label="${label}">
${primaryToolbar.length
? html`<div class="pf-c-toolbar__content" part="toolbar-primary">
? html`<div class="pf-c-toolbar__content" name="toolbar-primary">
${primaryToolbar}
</div>`
: nothing}
<div class="pf-c-toolbar__content" part="toolbar-secondary">
<div class="pf-c-toolbar__content" name="toolbar-secondary">
<div class="pf-c-toolbar__group">
${this.renderToolbar()} ${this.renderToolbarSelected()}
</div>
@@ -843,7 +804,6 @@ export abstract class Table<T extends object>
return html` <ak-table-search
class="pf-c-toolbar__item pf-m-search-filter ${isQL ? "ql" : ""}"
part="toolbar-search"
.defaultValue=${this.search}
label=${ifDefined(this.searchLabel)}
placeholder=${ifDefined(this.searchPlaceholder)}
@@ -982,19 +942,15 @@ export abstract class Table<T extends object>
<tr class="pf-c-table__header-row">
${this.checkbox ? this.renderAllOnThisPageCheckbox() : nothing}
${this.expandable ? html`<td aria-hidden="true"></td>` : nothing}
${this.columns.map((column, idx) => {
const [label, orderBy, ariaLabel] = column;
const columnID = this.#columnIDs.get(column) ?? `column-${idx}`;
return renderTableColumn({
${this.columns.map(([label, orderBy, ariaLabel], idx) =>
renderTableColumn({
label,
id: columnID,
ariaLabel,
orderBy,
table: this,
columnIndex: idx,
});
})}
}),
)}
</tr>
</thead>
${this.renderRows()}

View File

@@ -31,12 +31,13 @@ function formatSortDirection(table: TableLike, orderBy?: string | null): SortDir
export type TableColumn = [label: string, orderBy?: string | null, ariaLabel?: string | null];
const formatColumnID = (columnIndex: number): string => `table-header-${columnIndex}`;
export interface TableColumnProps {
label: string;
ariaLabel?: string | null;
orderBy?: string | null;
table: TableLike;
id?: string;
columnIndex: number;
}
@@ -45,7 +46,7 @@ export function renderTableColumn({
ariaLabel,
orderBy,
table,
id,
columnIndex,
}: TableColumnProps): TemplateResult {
const classes = {
"presentational": !label,
@@ -80,8 +81,9 @@ export function renderTableColumn({
: html`${label}`;
return html`<th
id=${ifPresent(id)}
id=${formatColumnID(columnIndex)}
aria-label=${ifPresent(resolvedLabel)}
data-column-id=${ifPresent(orderBy)}
scope="col"
aria-sort=${direction}
class="${classMap(classes)}"

View File

@@ -20,7 +20,6 @@ export abstract class TablePage<T extends object> extends Table<T> {
PFSidebar,
css`
.pf-c-sidebar__panel {
--pf-c-sidebar__panel--Position: static;
flex: 0 1 25%;
}
.pf-c-sidebar__content {

View File

@@ -83,28 +83,29 @@ export class ScheduleList extends Table<Schedule> {
if (this.relObjId !== undefined) {
return nothing;
}
return html`<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.showOnlyStandalone}
@change=${this.#toggleShowOnlyStandalone}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"> </i>
return html`&nbsp;
<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.showOnlyStandalone}
@change=${this.#toggleShowOnlyStandalone}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"> </i>
</span>
</span>
</span>
<span class="pf-c-switch__label">
${msg("Show only standalone schedules")}
</span>
</label>
<span class="pf-c-switch__label">
${msg("Show only standalone schedules")}
</span>
</label>
</div>
</div>
</div>
</div>`;
</div>`;
}
row(item: Schedule): SlottedTemplateResult[] {

View File

@@ -66,9 +66,6 @@ export class TaskList extends Table<Task> {
? [
TasksTasksListAggregatedStatusEnum.Queued,
TasksTasksListAggregatedStatusEnum.Consumed,
TasksTasksListAggregatedStatusEnum.Preprocess,
TasksTasksListAggregatedStatusEnum.Running,
TasksTasksListAggregatedStatusEnum.Postprocess,
TasksTasksListAggregatedStatusEnum.Rejected,
TasksTasksListAggregatedStatusEnum.Warning,
TasksTasksListAggregatedStatusEnum.Error,
@@ -109,44 +106,47 @@ export class TaskList extends Table<Task> {
];
renderToolbarAfter(): TemplateResult {
return html`<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
${this.relObjId === undefined
? html` <label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.showOnlyStandalone}
@change=${this.#toggleShowOnlyStandalone}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"> </i>
return html`&nbsp;
<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group">
${this.relObjId === undefined
? html` <label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.showOnlyStandalone}
@change=${this.#toggleShowOnlyStandalone}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"> </i>
</span>
</span>
</span>
<span class="pf-c-switch__label">
${msg("Show only standalone tasks")}
</span>
</label>`
: nothing}
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.excludeSuccessful}
@change=${this.#toggleExcludeSuccessful}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"> </i>
<span class="pf-c-switch__label">
${msg("Show only standalone tasks")}
</span>
</label>`
: nothing}
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.excludeSuccessful}
@change=${this.#toggleExcludeSuccessful}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"> </i>
</span>
</span>
</span>
<span class="pf-c-switch__label"> ${msg("Exclude successful tasks")} </span>
</label>
<span class="pf-c-switch__label">
${msg("Exclude successful tasks")}
</span>
</label>
</div>
</div>
</div>
</div>`;
</div>`;
}
row(item: Task): SlottedTemplateResult[] {

View File

@@ -1,82 +0,0 @@
import "#elements/cards/AggregateCard";
import { DEFAULT_CONFIG } from "#common/api/config";
import { AKElement } from "#elements/Base";
import { GlobalTaskStatus, TasksApi } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { css, CSSResult, html, nothing } from "lit";
import { customElement, state } from "lit/decorators.js";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
@customElement("ak-task-overview")
export class TaskOverview extends AKElement {
static styles: CSSResult[] = [
PFBase,
PFGrid,
PFCard,
PFPage,
css`
.pf-m-no-padding-bottom {
padding-bottom: 0;
}
`,
];
@state()
status?: GlobalTaskStatus;
async firstUpdated() {
this.status = await new TasksApi(DEFAULT_CONFIG).tasksTasksStatusRetrieve();
}
render() {
if (!this.status) return nothing;
return html`<section class="pf-c-page__main-section pf-m-no-padding-bottom">
<div
class="pf-l-grid pf-m-gutter pf-m-all-6-col-on-sm pf-m-all-4-col-on-md pf-m-all-3-col-on-lg pf-m-all-3-col-on-xl"
>
<ak-aggregate-card
class="pf-l-grid__item"
icon="pf-icon pf-icon-in-progress"
label=${msg("Running tasks")}
>
${this.status.running + this.status.preprocess + this.status.postprocess}
</ak-aggregate-card>
<ak-aggregate-card
class="pf-l-grid__item"
icon="pf-icon pf-icon-pending"
label=${msg("Queued tasks")}
>
${this.status.queued + this.status.consumed}
</ak-aggregate-card>
<ak-aggregate-card
class="pf-l-grid__item"
icon="fa fa-check-circle"
label=${msg("Successful tasks")}
>
${this.status.done + this.status.info + this.status.warning}
</ak-aggregate-card>
<ak-aggregate-card
class="pf-l-grid__item"
icon="fa fa-exclamation-triangle"
label=${msg("Error tasks")}
>
${this.status.error + this.status.rejected}
</ak-aggregate-card>
</div>
</section>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-task-overview": TaskOverview;
}
}

View File

@@ -34,19 +34,7 @@ export class TaskStatus extends AKElement {
case TasksTasksListAggregatedStatusEnum.Consumed:
case TaskAggregatedStatusEnum.Consumed:
case LastTaskStatusEnum.Consumed:
return html`<ak-label color=${PFColor.Blue}>${msg("Consumed")}</ak-label>`;
case TasksTasksListAggregatedStatusEnum.Preprocess:
case TaskAggregatedStatusEnum.Preprocess:
case LastTaskStatusEnum.Preprocess:
return html`<ak-label color=${PFColor.Blue}>${msg("Pre-processing")}</ak-label>`;
case TasksTasksListAggregatedStatusEnum.Running:
case TaskAggregatedStatusEnum.Running:
case LastTaskStatusEnum.Running:
return html`<ak-label color=${PFColor.Blue}>${msg("Running")}</ak-label>`;
case TasksTasksListAggregatedStatusEnum.Postprocess:
case TaskAggregatedStatusEnum.Postprocess:
case LastTaskStatusEnum.Postprocess:
return html`<ak-label color=${PFColor.Blue}>${msg("Post-processing")}</ak-label>`;
case TasksTasksListAggregatedStatusEnum.Done:
case TaskAggregatedStatusEnum.Done:
case LastTaskStatusEnum.Done:

View File

@@ -48,38 +48,19 @@ export class FocusTarget<T extends HTMLElement = HTMLElement> {
public focus = (options?: FocusOptions): void => {
const { target } = this;
if (!target) {
console.debug("FocusTarget: Skipping focus, no target", target);
return;
}
if (document.activeElement === target) {
console.debug("FocusTarget: Target is already focused", target);
return;
}
if (!target) return;
if (document.activeElement === target) return;
// Despite our type definitions, this method isn't available in all browsers,
// so we fallback to assuming the element is visible.
const visible = target.checkVisibility?.() ?? true;
if (!visible) {
console.debug("FocusTarget: Skipping focus, target is not visible", target);
return;
}
if (!visible) return;
if (typeof target.focus !== "function") {
console.debug("FocusTarget: Skipping focus, target has no focus method", target);
return;
}
target.focus(options);
target.focus?.(options);
};
public toRef() {
return ref(this.reference);
}
public toEventListener(options?: FocusOptions) {
return () => this.focus(options);
}
}

View File

@@ -48,15 +48,6 @@ import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
const FlowLayoutClasses = {
[FlowLayoutEnum.ContentLeft]: "pf-c-login__container",
[FlowLayoutEnum.ContentRight]: "pf-c-login__container content-right",
[FlowLayoutEnum.SidebarLeft]: "ak-login-container",
[FlowLayoutEnum.SidebarRight]: "ak-login-container",
[FlowLayoutEnum.Stacked]: "ak-login-container",
[FlowLayoutEnum.UnknownDefaultOpenApi]: "ak-login-container",
} as const satisfies Record<FlowLayoutEnum, string>;
@customElement("ak-flow-executor")
export class FlowExecutor
extends WithCapabilitiesConfig(WithBrandConfig(Interface))
@@ -82,12 +73,7 @@ export class FlowExecutor
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
@media (max-width: 768px) {
background: var(--pf-c-login__main--BackgroundColor) !important;
}
}
.ak-hidden {
display: none;
}
@@ -99,7 +85,7 @@ export class FlowExecutor
}
/* layouts */
@media (min-height: 60rem) {
.pf-c-login[data-layout="stacked"] .pf-c-login__main {
.pf-c-login.stacked .pf-c-login__main {
margin-top: 13rem;
}
}
@@ -109,34 +95,33 @@ export class FlowExecutor
"footer main"
". main";
}
.pf-c-login[data-layout="sidebar_left"] {
.pf-c-login.sidebar_left {
justify-content: flex-start;
padding-top: 0;
padding-bottom: 0;
}
.pf-c-login[data-layout="sidebar_left"] .ak-login-container,
.pf-c-login[data-layout="sidebar_right"] .ak-login-container {
height: 100%;
min-height: 100dvh;
.pf-c-login.sidebar_left .ak-login-container,
.pf-c-login.sidebar_right .ak-login-container {
height: 100vh;
background-color: var(--pf-c-login__main--BackgroundColor);
padding-inline: var(--pf-global--spacer--lg);
padding-block-end: var(--pf-global--spacer--xs);
padding-left: var(--pf-global--spacer--lg);
padding-right: var(--pf-global--spacer--lg);
}
.pf-c-login[data-layout="sidebar_left"] .pf-c-list,
.pf-c-login[data-layout="sidebar_right"] .pf-c-list {
.pf-c-login.sidebar_left .pf-c-list,
.pf-c-login.sidebar_right .pf-c-list {
color: #000;
}
.pf-c-login[data-layout="sidebar_right"] {
.pf-c-login.sidebar_right {
justify-content: flex-end;
padding-top: 0;
padding-bottom: 0;
}
:host([theme="dark"]) .pf-c-login[data-layout="sidebar_left"] .ak-login-container,
:host([theme="dark"]) .pf-c-login[data-layout="sidebar_right"] .ak-login-container {
:host([theme="dark"]) .pf-c-login.sidebar_left .ak-login-container,
:host([theme="dark"]) .pf-c-login.sidebar_right .ak-login-container {
background-color: var(--ak-dark-background);
}
:host([theme="dark"]) .pf-c-login[data-layout="sidebar_left"] .pf-c-list,
:host([theme="dark"]) .pf-c-login[data-layout="sidebar_right"] .pf-c-list {
:host([theme="dark"]) .pf-c-login.sidebar_left .pf-c-list,
:host([theme="dark"]) .pf-c-login.sidebar_right .pf-c-list {
color: var(--ak-dark-foreground);
}
.pf-c-brand {
@@ -367,10 +352,26 @@ export class FlowExecutor
//#region Render
get layout(): FlowLayoutEnum {
return (
this.challenge?.flowInfo?.layout || globalAK()?.flow?.layout || FlowLayoutEnum.Stacked
);
getLayout(): string {
const prefilledFlow = globalAK()?.flow?.layout || FlowLayoutEnum.Stacked;
if (this.challenge) {
return this.challenge?.flowInfo?.layout || prefilledFlow;
}
return prefilledFlow;
}
getLayoutClass(): string {
const layout = this.getLayout();
switch (layout) {
case FlowLayoutEnum.ContentLeft:
return "pf-c-login__container";
case FlowLayoutEnum.ContentRight:
return "pf-c-login__container content-right";
case FlowLayoutEnum.Stacked:
default:
return "ak-login-container";
}
}
async renderChallenge(): Promise<TemplateResult> {
@@ -547,7 +548,6 @@ export class FlowExecutor
return import("#flow/FlowInspector").then(
() =>
html`<ak-flow-inspector
id="flow-inspector"
class="pf-c-drawer__panel pf-m-width-33"
.flowSlug=${this.flowSlug}
></ak-flow-inspector>`,
@@ -555,25 +555,16 @@ export class FlowExecutor
}
render(): TemplateResult {
const { layout } = this;
return html`<ak-locale-context>
<div class="pf-c-background-image" part="background-image"></div>
<div class="pf-c-page__drawer" part="page-drawer">
<div
class="pf-c-drawer ${this.inspectorOpen ? "pf-m-expanded" : "pf-m-collapsed"}"
part="drawer"
>
<div class="pf-c-drawer__main" part="drawer-main">
<div class="pf-c-drawer__content" part="drawer-content">
<div class="pf-c-drawer__body" part="drawer-body">
<div class="pf-c-login" data-layout=${layout} part="flow">
<div class=${FlowLayoutClasses[layout]} part="flow-container">
<main
class="pf-c-login__main"
aria-label=${msg("Authentication form")}
part="flow-main"
>
return html` <ak-locale-context>
<div class="pf-c-background-image"></div>
<div class="pf-c-page__drawer">
<div class="pf-c-drawer ${this.inspectorOpen ? "pf-m-expanded" : "pf-m-collapsed"}">
<div class="pf-c-drawer__main">
<div class="pf-c-drawer__content">
<div class="pf-c-drawer__body">
<div class="pf-c-login ${this.getLayout()}">
<div class="${this.getLayoutClass()}">
<div class="pf-c-login__main">
${this.loading && this.challenge
? html`<ak-loading-overlay></ak-loading-overlay>`
: nothing}
@@ -583,15 +574,11 @@ export class FlowExecutor
<img
src="${themeImage(this.brandingLogo)}"
alt="${msg("authentik Logo")}"
role="presentation"
/>
</div>
${until(this.renderChallenge())}
</main>
</div>
<ak-brand-links
part="brand-links"
role="contentinfo"
aria-label=${msg("Site footer")}
class="pf-c-login__footer"
.links=${this.brandingFooterLinks}
></ak-brand-links>
@@ -601,12 +588,7 @@ export class FlowExecutor
</div>
${this.inspectorAvailable && !this.inspectorOpen
? html`<button
aria-label=${this.inspectorOpen
? msg("Close flow inspector")
: msg("Open flow inspector")}
aria-expanded=${this.inspectorOpen ? "true" : "false"}
class="inspector-toggle pf-c-button pf-m-primary"
aria-controls="flow-inspector"
@click=${() => {
this.inspectorOpen = true;
}}

View File

@@ -21,10 +21,6 @@ import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper
import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
function stringify(obj: unknown): string {
return JSON.stringify(obj, null, 4);
}
@customElement("ak-flow-inspector")
export class FlowInspector extends AKElement {
@property()
@@ -46,28 +42,12 @@ export class FlowInspector extends AKElement {
PFProgressStepper,
css`
.pf-c-drawer__body {
height: 100dvh;
min-height: 100vh;
max-height: 100vh;
}
:host {
background-color: var(--pf-c-notification-drawer--BackgroundColor) !important;
}
.pf-c-notification-drawer__body {
/* compatibility-mode-fix */
& {
padding-inline: var(--pf-global--spacer--md);
padding-block: var(--pf-global--spacer--xs);
}
.pf-l-stack__item:last-child {
padding-block-end: var(--pf-global--spacer--md);
}
}
code.break {
word-break: break-all;
}
pre {
word-break: break-all;
overflow-x: hidden;
@@ -111,8 +91,8 @@ export class FlowInspector extends AKElement {
return stage;
}
protected renderHeader() {
return html`<div class="pf-c-notification-drawer__header">
renderHeader() {
return html` <div class="pf-c-notification-drawer__header">
<div class="text">
<h1 class="pf-c-notification-drawer__header-title">${msg("Flow inspector")}</h1>
</div>
@@ -129,7 +109,7 @@ export class FlowInspector extends AKElement {
}}
class="pf-c-button pf-m-plain"
type="button"
aria-label=${msg("Close flow inspector")}
aria-label=${msg("Close")}
>
<i class="fas fa-times" aria-hidden="true"></i>
</button>
@@ -138,11 +118,8 @@ export class FlowInspector extends AKElement {
</div>`;
}
protected renderAccessDenied(): TemplateResult {
return html`<aside
aria-label=${msg("Flow inspector")}
class="pf-c-drawer__body pf-m-no-padding"
>
renderAccessDenied(): TemplateResult {
return html`<div class="pf-c-drawer__body pf-m-no-padding">
<div class="pf-c-notification-drawer">
${this.renderHeader()}
<div class="pf-c-notification-drawer__body">
@@ -155,171 +132,209 @@ export class FlowInspector extends AKElement {
</div>
</div>
</div>
</aside>`;
</div>`;
}
protected renderNextStage({ currentPlan, isCompleted }: FlowInspection): TemplateResult {
return html`<fieldset class="pf-c-card">
<legend class="pf-c-card__title">${msg("Next stage")}</legend>
<div class="pf-c-card__body">
<dl class="pf-c-description-list">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("Stage name")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${currentPlan?.nextPlannedStage?.stageObj?.name || "-"}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("Stage kind")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${currentPlan?.nextPlannedStage?.stageObj?.verboseName || "-"}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("Stage object")}</span>
</dt>
<dd class="pf-c-description-list__description">
${isCompleted
? html`<div class="pf-c-description-list__text">
${msg("This flow is completed.")}
</div>`
: html`<ak-expand>
<pre class="pf-c-description-list__text">
${stringify(this.getStage(currentPlan?.nextPlannedStage?.stageObj))}</pre
>
</ak-expand>`}
</dd>
</div>
</dl>
</div>
</fieldset>`;
}
protected renderPlanHistory({
plans,
isCompleted,
currentPlan,
}: FlowInspection): TemplateResult {
return html`<fieldset class="pf-c-card">
<legend class="pf-c-card__title">${msg("Plan history")}</legend>
<div class="pf-c-card__body">
<ol class="pf-c-progress-stepper pf-m-vertical">
${plans.map((plan) => {
return html`<li class="pf-c-progress-stepper__step pf-m-success">
<div class="pf-c-progress-stepper__step-connector">
<span class="pf-c-progress-stepper__step-icon">
<i class="fas fa-check-circle" aria-hidden="true"></i>
</span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div class="pf-c-progress-stepper__step-title">
${plan.currentStage.stageObj?.name}
</div>
<div class="pf-c-progress-stepper__step-description">
${plan.currentStage.stageObj?.verboseName}
</div>
</div>
</li> `;
})}
${currentPlan?.currentStage && !isCompleted
? html`<li class="pf-c-progress-stepper__step pf-m-current pf-m-info">
<div class="pf-c-progress-stepper__step-connector">
<span class="pf-c-progress-stepper__step-icon">
<i
class="pficon pf-icon-resources-full"
aria-hidden="true"
></i>
</span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div class="pf-c-progress-stepper__step-title">
${currentPlan?.currentStage?.stageObj?.name}
</div>
<div class="pf-c-progress-stepper__step-description">
${currentPlan?.currentStage?.stageObj?.verboseName}
</div>
</div>
</li>`
: nothing}
${currentPlan?.nextPlannedStage && !isCompleted
? html`<li class="pf-c-progress-stepper__step pf-m-pending">
<div class="pf-c-progress-stepper__step-connector">
<span class="pf-c-progress-stepper__step-icon"></span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div class="pf-c-progress-stepper__step-title">
${currentPlan.nextPlannedStage.stageObj?.name}
</div>
<div class="pf-c-progress-stepper__step-description">
${currentPlan?.nextPlannedStage?.stageObj?.verboseName}
</div>
</div>
</li>`
: nothing}
</ol>
</div>
</fieldset>`;
}
protected renderCurrentPlan({ currentPlan }: FlowInspection): TemplateResult {
return html`<fieldset class="pf-c-card">
<legend class="pf-c-card__title">${msg("Current plan context")}</legend>
<pre class="pf-c-card__body"><code>${stringify(currentPlan?.planContext)}</code></pre>
</fieldset>`;
}
protected renderSession({ currentPlan }: FlowInspection): TemplateResult {
return html`<fieldset class="pf-c-card">
<legend class="pf-c-card__title">${msg("Session ID")}</legend>
<div class="pf-c-card__body">
<code class="break"> ${currentPlan?.sessionId} </code>
</div>
</fieldset>`;
}
protected render(): TemplateResult {
render(): TemplateResult {
if (this.error) {
return this.renderAccessDenied();
}
if (!this.state) {
this.advanceHandler();
return html`<aside
aria-label=${msg("Flow inspector loading")}
class="pf-c-drawer__body pf-m-no-padding"
>
return html`<div class="pf-c-drawer__body pf-m-no-padding">
<div class="pf-c-notification-drawer">
${this.renderHeader()}
<div class="pf-c-notification-drawer__body"></div>
<ak-empty-state loading> </ak-empty-state>
</div>
</aside>`;
</div>`;
}
return html`<aside
aria-label=${msg("Flow inspector")}
class="pf-c-drawer__body pf-m-no-padding"
>
return html`<div class="pf-c-drawer__body pf-m-no-padding">
<div class="pf-c-notification-drawer">
${this.renderHeader()}
<div class="pf-c-notification-drawer__body">
<div class="pf-l-stack pf-m-gutter">
<div class="pf-l-stack__item">${this.renderNextStage(this.state)}</div>
<div class="pf-l-stack__item">${this.renderPlanHistory(this.state)}</div>
<div class="pf-l-stack__item">${this.renderCurrentPlan(this.state)}</div>
<div class="pf-l-stack__item">${this.renderSession(this.state)}</div>
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__title">${msg("Next stage")}</div>
</div>
<div class="pf-c-card__body">
<dl class="pf-c-description-list">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("Stage name")}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.state.currentPlan?.nextPlannedStage
?.stageObj?.name || "-"}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("Stage kind")}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.state.currentPlan?.nextPlannedStage
?.stageObj?.verboseName || "-"}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("Stage object")}</span
>
</dt>
<dd class="pf-c-description-list__description">
${this.state.isCompleted
? html` <div
class="pf-c-description-list__text"
>
${msg("This flow is completed.")}
</div>`
: html`<ak-expand>
<pre class="pf-c-description-list__text">
${JSON.stringify(this.getStage(this.state.currentPlan?.nextPlannedStage?.stageObj), null, 4)}</pre
>
</ak-expand>`}
</dd>
</div>
</dl>
</div>
</div>
</div>
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__title">${msg("Plan history")}</div>
</div>
<div class="pf-c-card__body">
<ol class="pf-c-progress-stepper pf-m-vertical">
${this.state.plans.map((plan) => {
return html`<li
class="pf-c-progress-stepper__step pf-m-success"
>
<div class="pf-c-progress-stepper__step-connector">
<span class="pf-c-progress-stepper__step-icon">
<i
class="fas fa-check-circle"
aria-hidden="true"
></i>
</span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div class="pf-c-progress-stepper__step-title">
${plan.currentStage.stageObj?.name}
</div>
<div
class="pf-c-progress-stepper__step-description"
>
${plan.currentStage.stageObj?.verboseName}
</div>
</div>
</li> `;
})}
${this.state.currentPlan?.currentStage &&
!this.state.isCompleted
? html` <li
class="pf-c-progress-stepper__step pf-m-current pf-m-info"
>
<div
class="pf-c-progress-stepper__step-connector"
>
<span
class="pf-c-progress-stepper__step-icon"
>
<i
class="pficon pf-icon-resources-full"
aria-hidden="true"
></i>
</span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div
class="pf-c-progress-stepper__step-title"
>
${this.state.currentPlan?.currentStage
?.stageObj?.name}
</div>
<div
class="pf-c-progress-stepper__step-description"
>
${this.state.currentPlan?.currentStage
?.stageObj?.verboseName}
</div>
</div>
</li>`
: nothing}
${this.state.currentPlan?.nextPlannedStage &&
!this.state.isCompleted
? html`<li
class="pf-c-progress-stepper__step pf-m-pending"
>
<div
class="pf-c-progress-stepper__step-connector"
>
<span
class="pf-c-progress-stepper__step-icon"
></span>
</div>
<div class="pf-c-progress-stepper__step-main">
<div
class="pf-c-progress-stepper__step-title"
>
${this.state.currentPlan.nextPlannedStage
.stageObj?.name}
</div>
<div
class="pf-c-progress-stepper__step-description"
>
${this.state.currentPlan?.nextPlannedStage
?.stageObj?.verboseName}
</div>
</div>
</li>`
: nothing}
</ol>
</div>
</div>
</div>
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__title">
${msg("Current plan context")}
</div>
</div>
<div class="pf-c-card__body">
<pre>
${JSON.stringify(this.state.currentPlan?.planContext, null, 4)}</pre
>
</div>
</div>
</div>
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__header">
<div class="pf-c-card__title">${msg("Session ID")}</div>
</div>
<div class="pf-c-card__body">
<code class="break">${this.state.currentPlan?.sessionId}</code>
</div>
</div>
</div>
</div>
</div>
</div>
</aside>`;
</div>`;
}
}

Some files were not shown because too many files have changed in this diff Show More