mirror of
https://github.com/goauthentik/authentik
synced 2026-05-12 01:47:06 +02:00
Compare commits
44 Commits
version/20
...
lit-jsx-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57c3ceae77 | ||
|
|
b58821cb49 | ||
|
|
e50cf1c150 | ||
|
|
4178717386 | ||
|
|
20d068f767 | ||
|
|
5b7a42e6d6 | ||
|
|
1398561142 | ||
|
|
55657e149b | ||
|
|
d5d7140631 | ||
|
|
17ff12f68f | ||
|
|
9c9a6e3d66 | ||
|
|
2cd81b2e78 | ||
|
|
bad426f694 | ||
|
|
6404fba2e4 | ||
|
|
c33b9f2d3f | ||
|
|
bac6e965f4 | ||
|
|
36cb4dc750 | ||
|
|
45d9945a3a | ||
|
|
23285ad664 | ||
|
|
91ab9503fd | ||
|
|
fb7802e6af | ||
|
|
0f13a63528 | ||
|
|
36daf4b519 | ||
|
|
5cc4793b84 | ||
|
|
a6063d4af4 | ||
|
|
8f450e6e14 | ||
|
|
a1fc0605e2 | ||
|
|
c886e4ff6b | ||
|
|
f91ebc2ad5 | ||
|
|
dbe7bfe58b | ||
|
|
05d4d207d7 | ||
|
|
11efc75451 | ||
|
|
4d2d020be1 | ||
|
|
9c0905d76d | ||
|
|
3ca94b2198 | ||
|
|
dbf51fb11f | ||
|
|
ad69eb955f | ||
|
|
c867ebc014 | ||
|
|
adea1e460c | ||
|
|
846c58e617 | ||
|
|
352079fc3c | ||
|
|
6786391732 | ||
|
|
4b3d08154d | ||
|
|
130fe4cac7 |
@@ -26,7 +26,7 @@ RUN npm run build && \
|
||||
npm run build:sfe
|
||||
|
||||
# Stage 2: Build go proxy
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25-bookworm AS go-builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
@@ -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.8 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.8.11 AS uv
|
||||
# Stage 5: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.6-slim-bookworm-fips AS python-base
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
|
||||
|
||||
| Version | Supported |
|
||||
| --------- | --------- |
|
||||
| 2025.4.x | ✅ |
|
||||
| 2025.6.x | ✅ |
|
||||
| 2025.8.x | ✅ |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from functools import lru_cache
|
||||
from os import environ
|
||||
|
||||
VERSION = "2025.8.1"
|
||||
VERSION = "2025.10.0-rc1"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
||||
@@ -63,6 +63,28 @@ class TestBrands(APITestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_brand_subdomain_same_suffix(self):
|
||||
"""Test Current brand API"""
|
||||
Brand.objects.all().delete()
|
||||
Brand.objects.create(domain="bar.baz", branding_title="custom")
|
||||
Brand.objects.create(domain="foo.bar.baz", branding_title="custom")
|
||||
self.assertJSONEqual(
|
||||
self.client.get(
|
||||
reverse("authentik_api:brand-current"), HTTP_HOST="foo.bar.baz"
|
||||
).content.decode(),
|
||||
{
|
||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
||||
"branding_title": "custom",
|
||||
"branding_custom_css": "",
|
||||
"matched_domain": "foo.bar.baz",
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
"default_locale": "",
|
||||
"flags": self.default_flags,
|
||||
},
|
||||
)
|
||||
|
||||
def test_fallback(self):
|
||||
"""Test fallback brand"""
|
||||
Brand.objects.all().delete()
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Any
|
||||
|
||||
from django.db.models import F, Q
|
||||
from django.db.models import Value as V
|
||||
from django.db.models.functions import Length
|
||||
from django.http.request import HttpRequest
|
||||
from django.utils.html import _json_script_escapes
|
||||
from django.utils.safestring import mark_safe
|
||||
@@ -20,9 +21,9 @@ DEFAULT_BRAND = Brand(domain="fallback")
|
||||
def get_brand_for_request(request: HttpRequest) -> Brand:
|
||||
"""Get brand object for current request"""
|
||||
db_brands = (
|
||||
Brand.objects.annotate(host_domain=V(request.get_host()))
|
||||
Brand.objects.annotate(host_domain=V(request.get_host()), match_length=Length("domain"))
|
||||
.filter(Q(host_domain__iendswith=F("domain")) | _q_default)
|
||||
.order_by("default")
|
||||
.order_by("-match_length", "default")
|
||||
)
|
||||
brands = list(db_brands.all())
|
||||
if len(brands) < 1:
|
||||
|
||||
@@ -154,7 +154,6 @@ worker:
|
||||
consumer_listen_timeout: "seconds=30"
|
||||
task_max_retries: 20
|
||||
task_default_time_limit: "minutes=10"
|
||||
lock_purge_interval: "minutes=1"
|
||||
task_purge_interval: "days=1"
|
||||
task_expiration: "days=30"
|
||||
scheduler_interval: "seconds=60"
|
||||
|
||||
@@ -76,7 +76,6 @@ class OutpostConfig:
|
||||
kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict)
|
||||
kubernetes_ingress_secret_name: str = field(default="authentik-outpost-tls")
|
||||
kubernetes_ingress_class_name: str | None = field(default=None)
|
||||
kubernetes_ingress_path_type: str | None = field(default=None)
|
||||
kubernetes_httproute_annotations: dict[str, str] = field(default_factory=dict)
|
||||
kubernetes_httproute_parent_refs: list[dict[str, str]] = field(default_factory=list)
|
||||
kubernetes_service_type: str = field(default="ClusterIP")
|
||||
@@ -152,7 +151,7 @@ class OutpostServiceConnection(ScheduledModel, models.Model):
|
||||
|
||||
state = cache.get(self.state_key, None)
|
||||
if not state:
|
||||
outpost_service_connection_monitor.send_with_options(args=(self.pk,), rel_obj=self)
|
||||
outpost_service_connection_monitor.send_with_options(args=(self.pk), rel_obj=self)
|
||||
return OutpostServiceConnectionState("", False)
|
||||
return state
|
||||
|
||||
|
||||
@@ -23,12 +23,7 @@ def user_session_deleted_oauth_backchannel_logout_and_tokens_removal(
|
||||
|
||||
backchannel_logout_notification_dispatch.send(
|
||||
revocations=[
|
||||
(
|
||||
token.provider_id,
|
||||
token.id_token.iss,
|
||||
token.id_token.sub,
|
||||
instance.session.session_key,
|
||||
)
|
||||
(token.provider_id, token.id_token.iss, token.session.user.uid)
|
||||
for token in access_tokens
|
||||
],
|
||||
)
|
||||
|
||||
@@ -14,19 +14,13 @@ LOGGER = get_logger()
|
||||
|
||||
|
||||
@actor(description=_("Send a back-channel logout request to the registered client"))
|
||||
def send_backchannel_logout_request(
|
||||
provider_pk: int,
|
||||
iss: str,
|
||||
sub: str | None = None,
|
||||
session_key: str | None = None,
|
||||
) -> bool:
|
||||
def send_backchannel_logout_request(provider_pk: int, iss: str, sub: str = None) -> bool:
|
||||
"""Send a back-channel logout request to the registered client
|
||||
|
||||
Args:
|
||||
provider_pk: The OAuth2 provider's primary key
|
||||
iss: The issuer URL for the logout token
|
||||
sub: The subject identifier to include in the logout token
|
||||
session_key: The authentik session key to hash and include in the logout token
|
||||
|
||||
Returns:
|
||||
bool: True if the request was sent successfully, False otherwise
|
||||
@@ -39,10 +33,11 @@ def send_backchannel_logout_request(
|
||||
return
|
||||
|
||||
# Generate the logout token
|
||||
logout_token = create_logout_token(provider, iss, sub, session_key)
|
||||
logout_token = create_logout_token(iss, provider, None, sub)
|
||||
|
||||
# Get the back-channel logout URI from the provider's dedicated backchannel_logout_uri field
|
||||
# Back-channel logout requires explicit configuration - no fallback to redirect URIs
|
||||
|
||||
backchannel_logout_uri = provider.backchannel_logout_uri
|
||||
if not backchannel_logout_uri:
|
||||
self.info("No back-channel logout URI found for provider")
|
||||
@@ -65,9 +60,9 @@ def send_backchannel_logout_request(
|
||||
def backchannel_logout_notification_dispatch(revocations: list, **kwargs):
|
||||
"""Handle backchannel logout notifications dispatched via signal"""
|
||||
for revocation in revocations:
|
||||
provider_pk, iss, sub, session_key = revocation
|
||||
provider_pk, iss, sub = revocation
|
||||
provider = OAuth2Provider.objects.filter(pk=provider_pk).first()
|
||||
send_backchannel_logout_request.send_with_options(
|
||||
args=(provider_pk, iss, sub, session_key),
|
||||
args=(provider_pk, iss, sub),
|
||||
rel_obj=provider,
|
||||
)
|
||||
|
||||
@@ -217,17 +217,17 @@ class HttpResponseRedirectScheme(HttpResponseRedirect):
|
||||
|
||||
|
||||
def create_logout_token(
|
||||
provider: OAuth2Provider,
|
||||
iss: str,
|
||||
sub: str | None = None,
|
||||
provider: OAuth2Provider,
|
||||
session_key: str | None = None,
|
||||
sub: str | None = None,
|
||||
) -> str:
|
||||
"""Create a logout token for Back-Channel Logout
|
||||
|
||||
As per https://openid.net/specs/openid-connect-backchannel-1_0.html
|
||||
"""
|
||||
|
||||
LOGGER.debug("Creating logout token", provider=provider, sub=sub)
|
||||
LOGGER.debug("Creating logout token", provider=provider, session_key=session_key, sub=sub)
|
||||
|
||||
# Create the logout token payload
|
||||
payload = {
|
||||
|
||||
@@ -127,9 +127,6 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
||||
and self.controller.outpost.config.kubernetes_ingress_secret_name
|
||||
):
|
||||
tls_hosts.append(external_host_name.hostname)
|
||||
path_type = "Prefix"
|
||||
if self.controller.outpost.config.kubernetes_ingress_path_type:
|
||||
path_type = self.controller.outpost.config.kubernetes_ingress_path_type
|
||||
if proxy_provider.mode in [
|
||||
ProxyMode.FORWARD_SINGLE,
|
||||
ProxyMode.FORWARD_DOMAIN,
|
||||
@@ -146,7 +143,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
||||
),
|
||||
),
|
||||
path="/outpost.goauthentik.io",
|
||||
path_type=path_type,
|
||||
path_type="Prefix",
|
||||
)
|
||||
]
|
||||
),
|
||||
@@ -164,7 +161,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
|
||||
),
|
||||
),
|
||||
path="/",
|
||||
path_type=path_type,
|
||||
path_type="Prefix",
|
||||
)
|
||||
]
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import importlib
|
||||
from collections import OrderedDict
|
||||
from hashlib import sha512
|
||||
from pathlib import Path
|
||||
from tempfile import gettempdir
|
||||
|
||||
import orjson
|
||||
from sentry_sdk import set_tag
|
||||
@@ -367,9 +368,6 @@ DRAMATIQ = {
|
||||
"broker_class": "authentik.tasks.broker.Broker",
|
||||
"channel_prefix": "authentik",
|
||||
"task_model": "authentik.tasks.models.Task",
|
||||
"lock_purge_interval": timedelta_from_string(
|
||||
CONFIG.get("worker.lock_purge_interval")
|
||||
).total_seconds(),
|
||||
"task_purge_interval": timedelta_from_string(
|
||||
CONFIG.get("worker.task_purge_interval")
|
||||
).total_seconds(),
|
||||
@@ -426,6 +424,7 @@ DRAMATIQ = {
|
||||
(
|
||||
"authentik.tasks.middleware.MetricsMiddleware",
|
||||
{
|
||||
"multiproc_dir": str(Path(gettempdir()) / "authentik_prometheus_tmp"),
|
||||
"prefix": "authentik",
|
||||
},
|
||||
),
|
||||
|
||||
@@ -35,12 +35,7 @@ class Command(TenantCommand):
|
||||
template_context={},
|
||||
)
|
||||
try:
|
||||
if not stage.use_global_settings:
|
||||
message.from_email = stage.from_address
|
||||
|
||||
send_mail.send(message.__dict__, stage.pk).get_result(block=True)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Test email sent to {options['to']}"))
|
||||
send_mail(message.__dict__, stage.pk)
|
||||
finally:
|
||||
if delete_stage:
|
||||
stage.delete()
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
"""Test email management commands"""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.core import mail
|
||||
from django.core.mail.backends.locmem import EmailBackend
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.stages.email.models import EmailStage
|
||||
|
||||
|
||||
class TestEmailManagementCommands(TestCase):
|
||||
"""Test email management commands"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = create_test_admin_user()
|
||||
|
||||
def test_test_email_command_with_stage(self):
|
||||
"""Test test_email command with specified stage"""
|
||||
EmailStage.objects.create(
|
||||
name="test-stage",
|
||||
from_address="test@authentik.local",
|
||||
host="localhost",
|
||||
port=25,
|
||||
)
|
||||
|
||||
with patch("authentik.stages.email.models.EmailStage.backend_class", EmailBackend):
|
||||
call_command("test_email", "test@example.com", stage="test-stage")
|
||||
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, "authentik Test-Email")
|
||||
self.assertEqual(mail.outbox[0].to, ["test@example.com"])
|
||||
|
||||
def test_test_email_command_with_global_settings(self):
|
||||
"""Test test_email command with global settings"""
|
||||
# Mock the backend to use Django's locmem backend
|
||||
with patch("authentik.stages.email.models.EmailStage.backend_class", EmailBackend):
|
||||
call_command("test_email", "test@example.com")
|
||||
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, "authentik Test-Email")
|
||||
self.assertEqual(mail.outbox[0].to, ["test@example.com"])
|
||||
|
||||
def test_test_email_command_invalid_stage(self):
|
||||
"""Test test_email command with invalid stage"""
|
||||
call_command("test_email", "test@example.com", stage="nonexistent")
|
||||
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
def test_test_email_command_with_custom_from(self):
|
||||
"""Test test_email command respects custom from address"""
|
||||
EmailStage.objects.create(
|
||||
name="test-stage",
|
||||
from_address="custom@authentik.local",
|
||||
host="localhost",
|
||||
port=25,
|
||||
)
|
||||
|
||||
with patch("authentik.stages.email.models.EmailStage.backend_class", EmailBackend):
|
||||
call_command("test_email", "test@example.com", stage="test-stage")
|
||||
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].from_email, "custom@authentik.local")
|
||||
self.assertEqual(mail.outbox[0].to, ["test@example.com"])
|
||||
@@ -100,15 +100,10 @@ class MessagesMiddleware(Middleware):
|
||||
TaskStatus.ERROR,
|
||||
exception,
|
||||
)
|
||||
event_kwargs = {
|
||||
"actor": task.actor_name,
|
||||
}
|
||||
if task.rel_obj:
|
||||
event_kwargs["rel_obj"] = task.rel_obj
|
||||
Event.new(
|
||||
EventAction.SYSTEM_TASK_EXCEPTION,
|
||||
message=f"Task {task.actor_name} encountered an error",
|
||||
**event_kwargs,
|
||||
actor=task.actor_name,
|
||||
).with_exception(exception).save()
|
||||
|
||||
def after_skip_message(self, broker: Broker, message: Message):
|
||||
@@ -156,6 +151,7 @@ class DescriptionMiddleware(Middleware):
|
||||
|
||||
|
||||
class _healthcheck_handler(BaseHTTPRequestHandler):
|
||||
|
||||
def log_request(self, code="-", size="-"):
|
||||
HEALTHCHECK_LOGGER.info(
|
||||
self.path,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2025.8.1 Blueprint schema",
|
||||
"title": "authentik 2025.10.0-rc1 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
|
||||
@@ -5,7 +5,7 @@ metadata:
|
||||
blueprints.goauthentik.io/system-bootstrap: "true"
|
||||
blueprints.goauthentik.io/system: "true"
|
||||
blueprints.goauthentik.io/description: |
|
||||
This blueprint configures the default admin user and group, and configures them for the [Automated install](https://docs.goauthentik.io/docs/install-config/automated-install?utm_source=bootstrap_blueprint).
|
||||
This blueprint configures the default admin user and group, and configures them for the [Automated install](https://goauthentik.io/docs/installation/automated-install).
|
||||
context:
|
||||
username: akadmin
|
||||
group_name: authentik Admins
|
||||
|
||||
@@ -48,7 +48,7 @@ services:
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_REDIS__HOST: redis
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.0-rc1}
|
||||
ports:
|
||||
- ${COMPOSE_PORT_HTTP:-9000}:9000
|
||||
- ${COMPOSE_PORT_HTTPS:-9443}:9443
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_REDIS__HOST: redis
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.8.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.0-rc1}
|
||||
restart: unless-stopped
|
||||
user: root
|
||||
volumes:
|
||||
|
||||
6
go.mod
6
go.mod
@@ -6,7 +6,7 @@ require (
|
||||
beryju.io/ldap v0.1.0
|
||||
github.com/avast/retry-go/v4 v4.6.1
|
||||
github.com/coreos/go-oidc/v3 v3.15.0
|
||||
github.com/getsentry/sentry-go v0.35.0
|
||||
github.com/getsentry/sentry-go v0.35.1
|
||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||
github.com/go-ldap/ldap/v3 v3.4.11
|
||||
github.com/go-openapi/runtime v0.28.0
|
||||
@@ -23,13 +23,13 @@ require (
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
github.com/prometheus/client_golang v1.23.0
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
github.com/redis/go-redis/v9 v9.12.1
|
||||
github.com/sethvargo/go-envconfig v1.3.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/wwt/guac v1.3.2
|
||||
goauthentik.io/api/v3 v3.2025064.8
|
||||
goauthentik.io/api/v3 v3.2025100.1
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.16.0
|
||||
|
||||
12
go.sum
12
go.sum
@@ -26,8 +26,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/getsentry/sentry-go v0.35.0 h1:+FJNlnjJsZMG3g0/rmmP7GiKjQoUF5EXfEtBwtPtkzY=
|
||||
github.com/getsentry/sentry-go v0.35.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||
github.com/getsentry/sentry-go v0.35.1 h1:iopow6UVLE2aXu46xKVIs8Z9D/YZkJrHkgozrxa+tOQ=
|
||||
github.com/getsentry/sentry-go v0.35.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
@@ -148,8 +148,8 @@ github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
|
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
|
||||
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -185,8 +185,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
goauthentik.io/api/v3 v3.2025064.8 h1:wgegkPUtGSrOR7+Rnd0cxLVU0cEea87BatjESa6BJv0=
|
||||
goauthentik.io/api/v3 v3.2025064.8/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
|
||||
goauthentik.io/api/v3 v3.2025100.1 h1:xOMnQ2j1MtrYJlO+8bHJ8MdFPyymqTZcLQ+5PTspdqA=
|
||||
goauthentik.io/api/v3 v3.2025100.1/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
|
||||
@@ -1 +1 @@
|
||||
2025.8.1
|
||||
2025.10.0-rc1
|
||||
@@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Stage 1: Build
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25-bookworm AS builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -68,11 +68,6 @@ function prepare_debug {
|
||||
chown authentik:authentik /unittest.xml
|
||||
}
|
||||
|
||||
if [[ -z "${PROMETHEUS_MULTIPROC_DIR}" ]]; then
|
||||
export PROMETHEUS_MULTIPROC_DIR="${TMPDIR:-/tmp}"
|
||||
fi
|
||||
mkdir -p "${PROMETHEUS_MULTIPROC_DIR}"
|
||||
|
||||
if [[ "$(python -m authentik.lib.config debugger 2>/dev/null)" == "True" ]]; then
|
||||
prepare_debug
|
||||
fi
|
||||
|
||||
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1024.0",
|
||||
"aws-cdk": "^2.1025.0",
|
||||
"cross-env": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -24,9 +24,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1024.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1024.0.tgz",
|
||||
"integrity": "sha512-hY0iVT2gPX/QOQXL7RSP2sqIRI/4BYU27vSmbhZxLEj//c3pkMkd9QpIHj7gOhyWC2gf6n5JuYPw27Dgw8FEdA==",
|
||||
"version": "2.1025.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1025.0.tgz",
|
||||
"integrity": "sha512-qKYM+RG5+U/UbGpjTt8ZaxBEfKJMPdOmtPtFNidsIGlrdIWSIFdNcFYi13zo33FkMk6ZFA6yBnjfDry3fNR+hQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1024.0",
|
||||
"aws-cdk": "^2.1025.0",
|
||||
"cross-env": "^10.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ Parameters:
|
||||
Description: authentik Docker image
|
||||
AuthentikVersion:
|
||||
Type: String
|
||||
Default: 2025.8.1
|
||||
Default: 2025.10.0-rc1
|
||||
Description: authentik Docker image tag
|
||||
AuthentikServerCPU:
|
||||
Type: Number
|
||||
|
||||
@@ -33,12 +33,15 @@ wait_for_db()
|
||||
_tmp = Path(gettempdir())
|
||||
worker_class = "lifecycle.worker.DjangoUvicornWorker"
|
||||
worker_tmp_dir = str(_tmp.joinpath("authentik_gunicorn_tmp"))
|
||||
prometheus_tmp_dir = str(_tmp.joinpath("authentik_prometheus_tmp"))
|
||||
|
||||
os.makedirs(worker_tmp_dir, exist_ok=True)
|
||||
os.makedirs(prometheus_tmp_dir, exist_ok=True)
|
||||
|
||||
bind = f"unix://{str(_tmp.joinpath('authentik-core.sock'))}"
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
|
||||
os.environ.setdefault("PROMETHEUS_MULTIPROC_DIR", prometheus_tmp_dir)
|
||||
|
||||
preload_app = True
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.8.1",
|
||||
"version": "2025.10.0-rc1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.8.1",
|
||||
"version": "2025.10.0-rc1",
|
||||
"dependencies": {
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.8.1",
|
||||
"version": "2025.10.0-rc1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -237,9 +237,6 @@ class _PostgresConsumer(Consumer):
|
||||
# Override because dramatiq doesn't allow us setting this manually
|
||||
self.timeout = Conf().worker["consumer_listen_timeout"]
|
||||
|
||||
self.lock_purge_interval = timezone.timedelta(seconds=Conf().lock_purge_interval)
|
||||
self.lock_purge_last_run = timezone.now()
|
||||
|
||||
self.task_purge_interval = timezone.timedelta(seconds=Conf().task_purge_interval)
|
||||
self.task_purge_last_run = timezone.now() - self.task_purge_interval
|
||||
|
||||
@@ -381,8 +378,6 @@ class _PostgresConsumer(Consumer):
|
||||
# Force creation of listen connection
|
||||
_ = self.listen_connection
|
||||
|
||||
self._purge_locks()
|
||||
|
||||
processing = len(self.in_processing)
|
||||
if processing >= self.prefetch:
|
||||
# Wait and don't consume the message, other worker will be faster
|
||||
@@ -420,26 +415,24 @@ class _PostgresConsumer(Consumer):
|
||||
)
|
||||
|
||||
# No message to process
|
||||
self._purge_locks()
|
||||
self._auto_purge()
|
||||
self._scheduler()
|
||||
|
||||
return None
|
||||
|
||||
def _purge_locks(self):
|
||||
if timezone.now() - self.lock_purge_last_run < self.lock_purge_interval:
|
||||
return
|
||||
while True:
|
||||
try:
|
||||
message_id = self.unlock_queue.get(block=False)
|
||||
except Empty:
|
||||
break
|
||||
return
|
||||
self.logger.debug("Unlocking message", message_id=message_id)
|
||||
with self.connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"SELECT pg_advisory_unlock(%s)", (self._get_message_lock_id(message_id),)
|
||||
)
|
||||
self.unlock_queue.task_done()
|
||||
self.lock_purge_last_run = timezone.now()
|
||||
|
||||
def _auto_purge(self):
|
||||
if timezone.now() - self.task_purge_last_run < self.task_purge_interval:
|
||||
@@ -451,7 +444,6 @@ class _PostgresConsumer(Consumer):
|
||||
result_expiry__lte=timezone.now(),
|
||||
).delete()
|
||||
self.logger.info("Purged messages in all queues", count=count)
|
||||
self.task_purge_last_run = timezone.now()
|
||||
|
||||
def _scheduler(self):
|
||||
if not self.scheduler:
|
||||
@@ -459,7 +451,6 @@ class _PostgresConsumer(Consumer):
|
||||
if timezone.now() - self.scheduler_last_run < self.scheduler_interval:
|
||||
return
|
||||
self.scheduler.run()
|
||||
self.schedule_last_run = timezone.now()
|
||||
|
||||
@raise_connection_error
|
||||
def close(self):
|
||||
@@ -474,7 +465,4 @@ class _PostgresConsumer(Consumer):
|
||||
if self._listen_connection is not None:
|
||||
conn = self._listen_connection
|
||||
self._listen_connection = None
|
||||
try:
|
||||
conn.close()
|
||||
except DatabaseError:
|
||||
pass
|
||||
conn.close()
|
||||
|
||||
@@ -56,10 +56,6 @@ class Conf:
|
||||
def task_model(self) -> str:
|
||||
return self.conf["task_model"]
|
||||
|
||||
@property
|
||||
def lock_purge_interval(self) -> int:
|
||||
return self.conf.get("lock_purge_interval", 60)
|
||||
|
||||
@property
|
||||
def task_purge_interval(self) -> int:
|
||||
# 24 hours
|
||||
|
||||
@@ -26,7 +26,7 @@ class HTTPServer(BaseHTTPServer):
|
||||
self.socket.close()
|
||||
|
||||
host, port = self.server_address[:2]
|
||||
if host == "0.0.0.0" and socket.has_dualstack_ipv6(): # nosec
|
||||
if host == "0.0.0.0": # nosec
|
||||
host = "::" # nosec
|
||||
|
||||
# Strip IPv6 brackets
|
||||
@@ -36,9 +36,7 @@ class HTTPServer(BaseHTTPServer):
|
||||
self.server_address = (host, port)
|
||||
|
||||
self.address_family = (
|
||||
socket.AF_INET6
|
||||
if socket.has_dualstack_ipv6() and isinstance(ip_address(host), IPv6Address)
|
||||
else socket.AF_INET
|
||||
socket.AF_INET6 if isinstance(ip_address(host), IPv6Address) else socket.AF_INET
|
||||
)
|
||||
|
||||
self.socket = socket.create_server(
|
||||
@@ -143,6 +141,7 @@ class MetricsMiddleware(Middleware):
|
||||
def __init__(
|
||||
self,
|
||||
prefix: str,
|
||||
multiproc_dir: str,
|
||||
labels: list[str] | None = None,
|
||||
):
|
||||
super().__init__()
|
||||
@@ -152,6 +151,9 @@ class MetricsMiddleware(Middleware):
|
||||
self.delayed_messages = set()
|
||||
self.message_start_times = {}
|
||||
|
||||
os.makedirs(multiproc_dir, exist_ok=True)
|
||||
os.environ.setdefault("PROMETHEUS_MULTIPROC_DIR", multiproc_dir)
|
||||
|
||||
@property
|
||||
def forks(self):
|
||||
from django_dramatiq_postgres.forks import worker_metrics
|
||||
|
||||
220
packages/esbuild-plugin-live-reload/package-lock.json
generated
220
packages/esbuild-plugin-live-reload/package-lock.json
generated
@@ -132,9 +132,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||
"integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
|
||||
"integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -149,9 +149,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
|
||||
"integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
|
||||
"integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -166,9 +166,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -183,9 +183,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -200,9 +200,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -217,9 +217,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -234,9 +234,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -251,9 +251,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -268,9 +268,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
|
||||
"integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
|
||||
"integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -285,9 +285,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -302,9 +302,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
|
||||
"integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
|
||||
"integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -319,9 +319,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
|
||||
"integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
|
||||
"integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -336,9 +336,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
|
||||
"integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
|
||||
"integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -353,9 +353,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
|
||||
"integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
|
||||
"integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -370,9 +370,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
|
||||
"integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
|
||||
"integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -387,9 +387,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
|
||||
"integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
|
||||
"integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -404,9 +404,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -421,9 +421,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -438,9 +438,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -455,9 +455,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -472,9 +472,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -489,9 +489,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -506,9 +506,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -523,9 +523,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
|
||||
"integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
|
||||
"integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -540,9 +540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
|
||||
"integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
|
||||
"integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -557,9 +557,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
|
||||
"integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
|
||||
"integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1269,9 +1269,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
|
||||
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
||||
"version": "0.25.9",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
|
||||
"integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -1282,32 +1282,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.8",
|
||||
"@esbuild/android-arm": "0.25.8",
|
||||
"@esbuild/android-arm64": "0.25.8",
|
||||
"@esbuild/android-x64": "0.25.8",
|
||||
"@esbuild/darwin-arm64": "0.25.8",
|
||||
"@esbuild/darwin-x64": "0.25.8",
|
||||
"@esbuild/freebsd-arm64": "0.25.8",
|
||||
"@esbuild/freebsd-x64": "0.25.8",
|
||||
"@esbuild/linux-arm": "0.25.8",
|
||||
"@esbuild/linux-arm64": "0.25.8",
|
||||
"@esbuild/linux-ia32": "0.25.8",
|
||||
"@esbuild/linux-loong64": "0.25.8",
|
||||
"@esbuild/linux-mips64el": "0.25.8",
|
||||
"@esbuild/linux-ppc64": "0.25.8",
|
||||
"@esbuild/linux-riscv64": "0.25.8",
|
||||
"@esbuild/linux-s390x": "0.25.8",
|
||||
"@esbuild/linux-x64": "0.25.8",
|
||||
"@esbuild/netbsd-arm64": "0.25.8",
|
||||
"@esbuild/netbsd-x64": "0.25.8",
|
||||
"@esbuild/openbsd-arm64": "0.25.8",
|
||||
"@esbuild/openbsd-x64": "0.25.8",
|
||||
"@esbuild/openharmony-arm64": "0.25.8",
|
||||
"@esbuild/sunos-x64": "0.25.8",
|
||||
"@esbuild/win32-arm64": "0.25.8",
|
||||
"@esbuild/win32-ia32": "0.25.8",
|
||||
"@esbuild/win32-x64": "0.25.8"
|
||||
"@esbuild/aix-ppc64": "0.25.9",
|
||||
"@esbuild/android-arm": "0.25.9",
|
||||
"@esbuild/android-arm64": "0.25.9",
|
||||
"@esbuild/android-x64": "0.25.9",
|
||||
"@esbuild/darwin-arm64": "0.25.9",
|
||||
"@esbuild/darwin-x64": "0.25.9",
|
||||
"@esbuild/freebsd-arm64": "0.25.9",
|
||||
"@esbuild/freebsd-x64": "0.25.9",
|
||||
"@esbuild/linux-arm": "0.25.9",
|
||||
"@esbuild/linux-arm64": "0.25.9",
|
||||
"@esbuild/linux-ia32": "0.25.9",
|
||||
"@esbuild/linux-loong64": "0.25.9",
|
||||
"@esbuild/linux-mips64el": "0.25.9",
|
||||
"@esbuild/linux-ppc64": "0.25.9",
|
||||
"@esbuild/linux-riscv64": "0.25.9",
|
||||
"@esbuild/linux-s390x": "0.25.9",
|
||||
"@esbuild/linux-x64": "0.25.9",
|
||||
"@esbuild/netbsd-arm64": "0.25.9",
|
||||
"@esbuild/netbsd-x64": "0.25.9",
|
||||
"@esbuild/openbsd-arm64": "0.25.9",
|
||||
"@esbuild/openbsd-x64": "0.25.9",
|
||||
"@esbuild/openharmony-arm64": "0.25.9",
|
||||
"@esbuild/sunos-x64": "0.25.9",
|
||||
"@esbuild/win32-arm64": "0.25.9",
|
||||
"@esbuild/win32-ia32": "0.25.9",
|
||||
"@esbuild/win32-x64": "0.25.9"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
@@ -2728,9 +2728,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typedoc-plugin-markdown": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.8.0.tgz",
|
||||
"integrity": "sha512-BQqXnT9PETe6WEFf8bcsvvGEGQHbwTo/BFyY+RUIsSB05Y0Wn56iF+fK1PY2OKJJIhV4kp4dp7osaP9Bm5a0Zw==",
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.8.1.tgz",
|
||||
"integrity": "sha512-ug7fc4j0SiJxSwBGLncpSo8tLvrT9VONvPUQqQDTKPxCoFQBADLli832RGPtj6sfSVJebNSrHZQRUdEryYH/7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
122
packages/eslint-config/package-lock.json
generated
122
packages/eslint-config/package-lock.json
generated
@@ -502,17 +502,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.0.tgz",
|
||||
"integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz",
|
||||
"integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/type-utils": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/type-utils": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -526,7 +526,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.39.0",
|
||||
"@typescript-eslint/parser": "^8.39.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@@ -542,16 +542,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.0.tgz",
|
||||
"integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz",
|
||||
"integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -567,14 +567,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz",
|
||||
"integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz",
|
||||
"integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.39.0",
|
||||
"@typescript-eslint/types": "^8.39.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.39.1",
|
||||
"@typescript-eslint/types": "^8.39.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -589,14 +589,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz",
|
||||
"integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz",
|
||||
"integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0"
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -607,9 +607,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz",
|
||||
"integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz",
|
||||
"integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -624,15 +624,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.0.tgz",
|
||||
"integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz",
|
||||
"integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -649,9 +649,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz",
|
||||
"integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz",
|
||||
"integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -663,16 +663,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz",
|
||||
"integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz",
|
||||
"integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.39.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/visitor-keys": "8.39.0",
|
||||
"@typescript-eslint/project-service": "8.39.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/visitor-keys": "8.39.1",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -731,16 +731,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz",
|
||||
"integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz",
|
||||
"integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0"
|
||||
"@typescript-eslint/scope-manager": "8.39.1",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -755,13 +755,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz",
|
||||
"integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz",
|
||||
"integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.39.0",
|
||||
"@typescript-eslint/types": "8.39.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4709,16 +4709,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.39.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.0.tgz",
|
||||
"integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==",
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz",
|
||||
"integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.39.0",
|
||||
"@typescript-eslint/parser": "8.39.0",
|
||||
"@typescript-eslint/typescript-estree": "8.39.0",
|
||||
"@typescript-eslint/utils": "8.39.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.39.1",
|
||||
"@typescript-eslint/parser": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
||||
67
packages/lit-jsx/e2e/index.ts
Normal file
67
packages/lit-jsx/e2e/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { LitNode } from "../types/lit-jsx.js";
|
||||
|
||||
import { test as base } from "@playwright/experimental-ct-react";
|
||||
import { Locator, Page } from "playwright/test";
|
||||
|
||||
export { expect } from "@playwright/test";
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
|
||||
export interface InnerMountOptions {}
|
||||
|
||||
async function innerMount(page: Page, componentRef: unknown, options = {}) {
|
||||
await page.waitForFunction(() => {
|
||||
// @ts-ignore
|
||||
return !!window.playwrightMount;
|
||||
});
|
||||
|
||||
const selector = await page.evaluate(
|
||||
async ({ component: component2 }) => {
|
||||
let rootElement = document.getElementById("root");
|
||||
|
||||
if (!rootElement) {
|
||||
rootElement = document.createElement("div");
|
||||
rootElement.id = "root";
|
||||
document.body.appendChild(rootElement);
|
||||
}
|
||||
|
||||
rootElement.textContent = "Test 123";
|
||||
return "#root >> internal:control=component";
|
||||
},
|
||||
{ component: componentRef },
|
||||
);
|
||||
return selector;
|
||||
}
|
||||
|
||||
interface E2EFixturesTestScope {
|
||||
render: (component: LitNode, options?: any) => Promise<Locator>;
|
||||
}
|
||||
|
||||
interface E2EWorkerScope {
|
||||
renderWorker: void;
|
||||
}
|
||||
|
||||
export const test = base.extend<E2EFixturesTestScope, E2EWorkerScope>({
|
||||
// renderWorker: [async ({ browser }, use, workerInfo) => {}, { scope: "worker" }],
|
||||
render: async ({ page }, use) => {
|
||||
await use(async (componentRef, options) => {
|
||||
const selector = await innerMount(page, componentRef, options);
|
||||
|
||||
const locator = page.locator(selector);
|
||||
|
||||
// Object.assign(locator, {
|
||||
// unmount: async () => {
|
||||
// await locator.evaluate(async () => {
|
||||
// const rootElement = document.getElementById("root");
|
||||
// await window.playwrightUnmount(rootElement);
|
||||
// });
|
||||
// },
|
||||
// update: async (options2) => {
|
||||
// if (isJsxComponent(options2)) return await innerUpdate(page, options2);
|
||||
// await innerUpdate(page, componentRef, options2);
|
||||
// },
|
||||
// });
|
||||
|
||||
return locator;
|
||||
});
|
||||
},
|
||||
});
|
||||
0
packages/lit-jsx/lib/jsx-dev-runtime.ts
Normal file
0
packages/lit-jsx/lib/jsx-dev-runtime.ts
Normal file
39
packages/lit-jsx/lib/jsx-runtime.ts
Normal file
39
packages/lit-jsx/lib/jsx-runtime.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { isCustomElementConstructor, isElementFactory } from "./predicates.js";
|
||||
import { ComponentProps, createElement } from "./utils.js";
|
||||
|
||||
import type * as Lit from "@goauthentik/lit-jsx/types/lit-jsx.d.ts";
|
||||
|
||||
/**
|
||||
* JSX factory for Lit elements.
|
||||
*/
|
||||
export function jsx(
|
||||
elementLike: Lit.ElementType | Lit.ElementFactoryLike,
|
||||
props: ComponentProps,
|
||||
): Lit.LitNode {
|
||||
console.log({ elementLike, props });
|
||||
if (isElementFactory(elementLike)) {
|
||||
if (isCustomElementConstructor(elementLike)) {
|
||||
const tagName = customElements.getName(elementLike);
|
||||
|
||||
if (!tagName) {
|
||||
throw new Error(`Custom element ${elementLike.name} is not registered`);
|
||||
}
|
||||
|
||||
// Render the custom web component as any other html element.
|
||||
return createElement(tagName, props);
|
||||
}
|
||||
|
||||
return elementLike(props);
|
||||
}
|
||||
|
||||
return createElement(elementLike, props);
|
||||
}
|
||||
|
||||
export { jsx as jsxAttr, jsx as jsxDEV, jsx as jsxEscape, jsx as jsxs, jsx as jsxTemplate };
|
||||
|
||||
/**
|
||||
* HTML Fragment factory for Lit elements.
|
||||
*/
|
||||
export function Fragment(fragment: Lit.Fragment): Lit.ElementType[] {
|
||||
return Array.isArray(fragment.children) ? fragment.children : [fragment.children];
|
||||
}
|
||||
15
packages/lit-jsx/lib/predicates.ts
Normal file
15
packages/lit-jsx/lib/predicates.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Type predicate to check if a given value is a custom element constructor.
|
||||
*/
|
||||
export function isElementFactory(elementLike: unknown): elementLike is Lit.ElementFactoryLike {
|
||||
return typeof elementLike === "function" && elementLike.prototype instanceof HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type predicate to check if a given value is a custom element constructor.
|
||||
*/
|
||||
export function isCustomElementConstructor<P = unknown, T extends HTMLElement = HTMLElement>(
|
||||
elementLike: Lit.ElementFactoryLike<P>,
|
||||
): elementLike is Lit.CustomElementConstructor<P, T> {
|
||||
return elementLike.prototype instanceof HTMLElement;
|
||||
}
|
||||
69
packages/lit-jsx/lib/utils.ts
Normal file
69
packages/lit-jsx/lib/utils.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { ref, RefOrCallback } from "lit/directives/ref.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { html, unsafeStatic } from "lit/static-html.js";
|
||||
|
||||
export type CSSProperties = Record<string, string | number>;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function parseProps(tagName: string, props: Record<PropertyKey, unknown>) {
|
||||
const spreadable: Record<PropertyKey, unknown> = {};
|
||||
|
||||
const ElementConstructor = customElements.get(tagName);
|
||||
|
||||
for (const [propName, value] of Object.entries(props)) {
|
||||
if (propName === "htmlFor") {
|
||||
spreadable.for = value;
|
||||
continue;
|
||||
}
|
||||
if (propName.startsWith("on")) {
|
||||
const eventName = propName.slice(2).toLowerCase();
|
||||
spreadable[`@${eventName}`] = value;
|
||||
} else if (typeof value === "boolean") {
|
||||
spreadable[`?${propName}`] = value;
|
||||
} else if (ElementConstructor) {
|
||||
spreadable[`.${propName}`] = value;
|
||||
} else {
|
||||
spreadable[`${propName}`] = value;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(spreadable);
|
||||
|
||||
return spreadable;
|
||||
}
|
||||
|
||||
export interface ComponentProps {
|
||||
className?: string;
|
||||
children?: unknown;
|
||||
ref?: RefOrCallback;
|
||||
style?: CSSProperties;
|
||||
[key: PropertyKey]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export function createElement<P = unknown>(
|
||||
tagName: string,
|
||||
{ className, children, ref: refProp, style, ...props }: ComponentProps & P,
|
||||
) {
|
||||
const tag = unsafeStatic(tagName);
|
||||
|
||||
const result = html`
|
||||
<${tag} class=${ifDefined(className)}
|
||||
${ref(refProp)}
|
||||
${spread(parseProps(tagName, props))}
|
||||
style=${ifDefined(style ? styleMap(style) : null)}
|
||||
>
|
||||
${children}
|
||||
</${tag}>
|
||||
`;
|
||||
|
||||
return result;
|
||||
}
|
||||
8135
packages/lit-jsx/package-lock.json
generated
Normal file
8135
packages/lit-jsx/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
83
packages/lit-jsx/package.json
Normal file
83
packages/lit-jsx/package.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"name": "@goauthentik/lit-jsx",
|
||||
"version": "1.0.0",
|
||||
"description": "JSX Runtime for Lit",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"compile": "tsc -b",
|
||||
"lint": "run-s lint:prettier:check lint:eslint:check",
|
||||
"lint:eslint:check": "eslint .",
|
||||
"lint:eslint:fix": "eslint --fix .",
|
||||
"lint:fix": "run-s lint:prettier:fix lint:eslint:fix",
|
||||
"lint:prettier": "eslint .",
|
||||
"lint:prettier:check": "prettier --cache --check -u .",
|
||||
"lint:prettier:fix": "prettier --cache --write -u .",
|
||||
"test": "vitest"
|
||||
},
|
||||
"main": "tsconfig.json",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"types": "./lib/types/lit-jsx.d.ts"
|
||||
},
|
||||
"./jsx-runtime": {
|
||||
"import": "./out/lib/jsx-runtime.js",
|
||||
"types": "./types/jsx-runtime.d.ts"
|
||||
},
|
||||
"./jsx-dev-runtime": {
|
||||
"import": "./out/lib/jsx-runtime.js",
|
||||
"types": "./types/jsx-runtime.d.ts"
|
||||
},
|
||||
"./types/*": {
|
||||
"types": "./types/*"
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
"#e2e": "./e2e/index.ts",
|
||||
"#e2e/*": "./e2e/*.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@goauthentik/eslint-config": "^1.0.5",
|
||||
"@goauthentik/prettier-config": "^3.1.0",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@lit-labs/ssr": "^3.3.1",
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@playwright/experimental-ct-react": "^1.54.2",
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^24.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.39.1",
|
||||
"@typescript-eslint/parser": "^8.39.1",
|
||||
"dompurify": "^3.2.6",
|
||||
"eslint": "^9.33.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"lit": "^3.3.1",
|
||||
"lit-html": "^3.3.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"playwright": "^1.54.2",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-packagejson": "^2.5.19",
|
||||
"trusted-types": "^2.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite": "^7.1.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"lit": "^3.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24"
|
||||
},
|
||||
"prettier": "@goauthentik/prettier-config",
|
||||
"overrides": {
|
||||
"format-imports": {
|
||||
"eslint": "^9.31.0"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
77
packages/lit-jsx/playwright-report/index.html
Normal file
77
packages/lit-jsx/playwright-report/index.html
Normal file
File diff suppressed because one or more lines are too long
43
packages/lit-jsx/playwright.config.js
Normal file
43
packages/lit-jsx/playwright.config.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @file Playwright configuration.
|
||||
*
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
*
|
||||
*/
|
||||
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
const CI = !!process.env.CI;
|
||||
|
||||
const baseURL = process.env.AK_TEST_RUNNER_PAGE_URL ?? "http://localhost:9000";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./test/browser",
|
||||
fullyParallel: true,
|
||||
forbidOnly: CI,
|
||||
retries: CI ? 2 : 0,
|
||||
workers: CI ? 1 : undefined,
|
||||
reporter: CI
|
||||
? "github"
|
||||
: [
|
||||
// ---
|
||||
["list", { printSteps: true }],
|
||||
["html", { open: "never" }],
|
||||
],
|
||||
use: {
|
||||
testIdAttribute: "data-test-id",
|
||||
baseURL,
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
6
packages/lit-jsx/playwright/index.html
Normal file
6
packages/lit-jsx/playwright/index.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./index.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
1
packages/lit-jsx/playwright/index.ts
Normal file
1
packages/lit-jsx/playwright/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
console.log("Preparing Playwright");
|
||||
12
packages/lit-jsx/test/browser/render.test.tsx
Normal file
12
packages/lit-jsx/test/browser/render.test.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { expect, test } from "#e2e";
|
||||
|
||||
test.describe("JSX Rendering", () => {
|
||||
test("Simple static markup can be rendered", async ({ page, render }) => {
|
||||
await render(
|
||||
// @ts-ignore - testing
|
||||
<p style={{ color: "red", fontSize: "12px" }}>Hello World</p>,
|
||||
);
|
||||
|
||||
await expect(page.getByText("Test 123")).toBeVisible();
|
||||
});
|
||||
});
|
||||
48
packages/lit-jsx/test/unit/elements.test.tsx
Normal file
48
packages/lit-jsx/test/unit/elements.test.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
/** @jsxImportSource @goauthentik/lit-jsx */
|
||||
|
||||
import { renderVariants } from "../utils.js";
|
||||
|
||||
import { test } from "vitest";
|
||||
|
||||
import { html } from "@lit-labs/ssr";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
|
||||
test("Simple static markup can be rendered", async ({ expect }) => {
|
||||
const [result, comparision] = await renderVariants(
|
||||
// @ts-ignore - testing
|
||||
<div>Hello World</div>,
|
||||
html`<div>Hello World</div>`,
|
||||
);
|
||||
|
||||
expect(result, "JSX element serialized to a matching string").toBe(comparision);
|
||||
});
|
||||
|
||||
test("`className` is rendered to the correct attribute", async ({ expect }) => {
|
||||
const [result, comparision] = await renderVariants(
|
||||
// @ts-ignore - testing
|
||||
<p className="one two">Hello World</p>,
|
||||
html`<p class="one two">Hello World</p>`,
|
||||
);
|
||||
|
||||
expect(result, "`className` is rendered to the correct attribute").toBe(comparision);
|
||||
});
|
||||
|
||||
test("`style` is rendered to the correct attribute", async ({ expect }) => {
|
||||
const [result, comparision] = await renderVariants(
|
||||
// @ts-ignore - testing
|
||||
<p style={{ color: "red", fontSize: "12px" }}>Hello World</p>,
|
||||
html`<p style=${styleMap({ color: "red", fontSize: "12px" })}>Hello World</p>`,
|
||||
);
|
||||
|
||||
expect(result, "style is rendered to the correct attribute").toBe(comparision);
|
||||
});
|
||||
|
||||
test("`style` is rendered to the correct attribute", async ({ expect }) => {
|
||||
const [result, comparision] = await renderVariants(
|
||||
// @ts-ignore - testing
|
||||
<p style={{ color: "red", fontSize: "12px" }}>Hello World</p>,
|
||||
html`<p style=${styleMap({ color: "red", fontSize: "12px" })}>Hello World</p>`,
|
||||
);
|
||||
|
||||
expect(result, "style is rendered to the correct attribute").toBe(comparision);
|
||||
});
|
||||
36
packages/lit-jsx/test/utils.ts
Normal file
36
packages/lit-jsx/test/utils.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type * as Lit from "@goauthentik/lit-jsx/types/lit-jsx.d.ts";
|
||||
|
||||
import createDOMPurify, { Config as DOMPurifyConfig, WindowLike } from "dompurify";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { format } from "prettier";
|
||||
|
||||
import { render, ServerRenderedTemplate } from "@lit-labs/ssr";
|
||||
import { collectResult } from "@lit-labs/ssr/lib/render-result.js";
|
||||
|
||||
export class Purifier {
|
||||
#window = new JSDOM("").window;
|
||||
#DOMPurify = createDOMPurify(this.#window as WindowLike);
|
||||
|
||||
public sanitize(html: string, config?: DOMPurifyConfig) {
|
||||
return this.#DOMPurify.sanitize(html, config);
|
||||
}
|
||||
}
|
||||
|
||||
const purifier = new Purifier();
|
||||
|
||||
export async function renderStaticLit(value: unknown) {
|
||||
const result = await collectResult(render(value));
|
||||
|
||||
const sanitized = purifier.sanitize(result);
|
||||
const formatted = await format(sanitized, {
|
||||
parser: "html",
|
||||
});
|
||||
|
||||
return formatted.trim();
|
||||
}
|
||||
|
||||
export function renderVariants(
|
||||
...inputs: Array<ServerRenderedTemplate | Lit.LitNode>
|
||||
): Promise<string[]> {
|
||||
return Promise.all(inputs.map((input) => renderStaticLit(input)));
|
||||
}
|
||||
15
packages/lit-jsx/tsconfig.json
Normal file
15
packages/lit-jsx/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "@goauthentik/tsconfig",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"checkJs": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"]
|
||||
},
|
||||
"exclude": [
|
||||
// ---
|
||||
"**/out/**/*",
|
||||
"**/dist/**/*",
|
||||
"storybook-static"
|
||||
]
|
||||
}
|
||||
75
packages/lit-jsx/types/jsx-runtime.d.ts
vendored
Normal file
75
packages/lit-jsx/types/jsx-runtime.d.ts
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file JSX runtime types for Lit.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
// /* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||
|
||||
// /// <reference types="./lit-jsx-runtime.d.ts" />
|
||||
// /**
|
||||
// * JSX runtime types for Lit.
|
||||
// */
|
||||
// export namespace JSX {
|
||||
// // type ElementType = Lit.JSX.ElementType;
|
||||
// // interface Element extends Lit.JSX.Element {}
|
||||
// // interface ElementClass extends Lit.JSX.ElementClass {}
|
||||
// // interface ElementAttributesProperty extends Lit.JSX.ElementAttributesProperty {}
|
||||
// // interface ElementChildrenAttribute extends Lit.JSX.ElementChildrenAttribute {}
|
||||
// // type LibraryManagedAttributes<C, P> = Lit.JSX.LibraryManagedAttributes<C, P>;
|
||||
// // interface IntrinsicAttributes extends Lit.JSX.IntrinsicAttributes {}
|
||||
// // interface IntrinsicClassAttributes<T> extends Lit.JSX.IntrinsicClassAttributes<T> {}
|
||||
// interface IntrinsicElements extends Lit.JSX.IntrinsicElements {}
|
||||
// }
|
||||
|
||||
import { JSX } from "./lit-jsx.js";
|
||||
|
||||
import { Attributes, ComponentChild, ComponentChildren, ComponentType, VNode } from "preact";
|
||||
|
||||
export { Fragment } from "preact";
|
||||
|
||||
export function jsx(
|
||||
type: string,
|
||||
props: JSX.HTMLAttributes &
|
||||
JSX.SVGAttributes &
|
||||
Record<string, any> & { children?: ComponentChild },
|
||||
key?: string,
|
||||
): VNode<any>;
|
||||
export function jsx<P>(
|
||||
type: ComponentType<P>,
|
||||
props: Attributes & P & { children?: ComponentChild },
|
||||
key?: string,
|
||||
): VNode<any>;
|
||||
|
||||
export function jsxs(
|
||||
type: string,
|
||||
props: JSX.HTMLAttributes &
|
||||
JSX.SVGAttributes &
|
||||
Record<string, any> & { children?: ComponentChild[] },
|
||||
key?: string,
|
||||
): VNode<any>;
|
||||
export function jsxs<P>(
|
||||
type: ComponentType<P>,
|
||||
props: Attributes & P & { children?: ComponentChild[] },
|
||||
key?: string,
|
||||
): VNode<any>;
|
||||
|
||||
export function jsxDEV(
|
||||
type: string,
|
||||
props: JSX.HTMLAttributes &
|
||||
JSX.SVGAttributes &
|
||||
Record<string, any> & { children?: ComponentChildren },
|
||||
key?: string,
|
||||
): VNode<any>;
|
||||
export function jsxDEV<P>(
|
||||
type: ComponentType<P>,
|
||||
props: Attributes & P & { children?: ComponentChildren },
|
||||
key?: string,
|
||||
): VNode<any>;
|
||||
|
||||
// These are not expected to be used manually, but by a JSX transform
|
||||
export function jsxTemplate(template: string[], ...expressions: any[]): VNode<any>;
|
||||
export function jsxAttr(name: string, value: any): string | null;
|
||||
export function jsxEscape<T>(value: T): string | null | VNode<any> | Array<string | null | VNode>;
|
||||
|
||||
export { JSX };
|
||||
318
packages/lit-jsx/types/lit-jsx.d.ts
vendored
Normal file
318
packages/lit-jsx/types/lit-jsx.d.ts
vendored
Normal file
@@ -0,0 +1,318 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/**
|
||||
* @file JSX runtime types for Lit.
|
||||
*/
|
||||
|
||||
import { nothing, TemplateResult } from "lit";
|
||||
|
||||
export = Lit;
|
||||
export as namespace Lit;
|
||||
|
||||
declare namespace Lit {
|
||||
namespace JSX {
|
||||
interface Element {
|
||||
type: string;
|
||||
}
|
||||
interface IntrinsicElements {
|
||||
div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructable element which returns a valid HTMLElement.
|
||||
*/
|
||||
type CustomElementConstructor<P = unknown, T extends HTMLElement = HTMLElement> = new (
|
||||
...args: unknown[]
|
||||
) => T;
|
||||
|
||||
/**
|
||||
* A function which given a props object, returns a Lit element.
|
||||
*/
|
||||
type ElementFactory<P = unknown> = (props: P) => ElementType;
|
||||
|
||||
/**
|
||||
* Either a constructor, or a function which returns an HTML element.
|
||||
*/
|
||||
type ElementFactoryLike<P = unknown> = CustomElementConstructor<P> | ElementFactory<P>;
|
||||
|
||||
type ElementType<
|
||||
P = unknown,
|
||||
Tag extends keyof JSX.IntrinsicElements = keyof JSX.IntrinsicElements,
|
||||
> =
|
||||
| { [K in Tag]: P extends JSX.IntrinsicElements[K] ? K : never }[Tag]
|
||||
| CustomElementConstructor<P>;
|
||||
|
||||
interface Fragment {
|
||||
children: Lit.ElementType | Lit.ElementType[];
|
||||
}
|
||||
|
||||
type LitNode = JSX.Element | ElementType | string | TemplateResult | typeof nothing;
|
||||
|
||||
type FC<P = unknown> = (props: P) => LitNode | LitNode[];
|
||||
|
||||
// Keep in sync with JSX namespace in ./jsx-runtime.d.ts and ./jsx-dev-runtime.d.ts
|
||||
}
|
||||
|
||||
// Users who only use Preact for SSR might not specify "dom" in their lib in tsconfig.json
|
||||
|
||||
// import * as preact from "preact";
|
||||
|
||||
// type Defaultize<Props, Defaults> =
|
||||
// // Distribute over unions
|
||||
// Props extends any // Make any properties included in Default optional
|
||||
// ? Partial<Pick<Props, Extract<keyof Props, keyof Defaults>>> & // Include the remaining properties from Props
|
||||
// Pick<Props, Exclude<keyof Props, keyof Defaults>>
|
||||
// : never;
|
||||
|
||||
// declare namespace JSX {
|
||||
// export type LibraryManagedAttributes<Component, Props> = Component extends {
|
||||
// defaultProps: infer Defaults;
|
||||
// }
|
||||
// ? Defaultize<Props, Defaults>
|
||||
// : Props;
|
||||
|
||||
// export interface IntrinsicAttributes {
|
||||
// key?: any;
|
||||
// }
|
||||
|
||||
// export type ElementType<P = any> =
|
||||
// | {
|
||||
// [K in keyof IntrinsicElements]: P extends IntrinsicElements[K] ? K : never;
|
||||
// }[keyof IntrinsicElements]
|
||||
// | preact.ComponentType<P>;
|
||||
// export interface Element extends preact.VNode<any> {}
|
||||
// export type ElementClass = preact.Component<any, any> | preact.FunctionComponent<any>;
|
||||
|
||||
// export interface ElementAttributesProperty {
|
||||
// props: any;
|
||||
// }
|
||||
|
||||
// export interface ElementChildrenAttribute {
|
||||
// children: any;
|
||||
// }
|
||||
|
||||
// export interface IntrinsicSVGElements {
|
||||
// svg: preact.SVGAttributes<SVGSVGElement>;
|
||||
// animate: preact.SVGAttributes<SVGAnimateElement>;
|
||||
// circle: preact.SVGAttributes<SVGCircleElement>;
|
||||
// animateMotion: preact.SVGAttributes<SVGAnimateMotionElement>;
|
||||
// animateTransform: preact.SVGAttributes<SVGAnimateTransformElement>;
|
||||
// clipPath: preact.SVGAttributes<SVGClipPathElement>;
|
||||
// defs: preact.SVGAttributes<SVGDefsElement>;
|
||||
// desc: preact.SVGAttributes<SVGDescElement>;
|
||||
// ellipse: preact.SVGAttributes<SVGEllipseElement>;
|
||||
// feBlend: preact.SVGAttributes<SVGFEBlendElement>;
|
||||
// feColorMatrix: preact.SVGAttributes<SVGFEColorMatrixElement>;
|
||||
// feComponentTransfer: preact.SVGAttributes<SVGFEComponentTransferElement>;
|
||||
// feComposite: preact.SVGAttributes<SVGFECompositeElement>;
|
||||
// feConvolveMatrix: preact.SVGAttributes<SVGFEConvolveMatrixElement>;
|
||||
// feDiffuseLighting: preact.SVGAttributes<SVGFEDiffuseLightingElement>;
|
||||
// feDisplacementMap: preact.SVGAttributes<SVGFEDisplacementMapElement>;
|
||||
// feDistantLight: preact.SVGAttributes<SVGFEDistantLightElement>;
|
||||
// feDropShadow: preact.SVGAttributes<SVGFEDropShadowElement>;
|
||||
// feFlood: preact.SVGAttributes<SVGFEFloodElement>;
|
||||
// feFuncA: preact.SVGAttributes<SVGFEFuncAElement>;
|
||||
// feFuncB: preact.SVGAttributes<SVGFEFuncBElement>;
|
||||
// feFuncG: preact.SVGAttributes<SVGFEFuncGElement>;
|
||||
// feFuncR: preact.SVGAttributes<SVGFEFuncRElement>;
|
||||
// feGaussianBlur: preact.SVGAttributes<SVGFEGaussianBlurElement>;
|
||||
// feImage: preact.SVGAttributes<SVGFEImageElement>;
|
||||
// feMerge: preact.SVGAttributes<SVGFEMergeElement>;
|
||||
// feMergeNode: preact.SVGAttributes<SVGFEMergeNodeElement>;
|
||||
// feMorphology: preact.SVGAttributes<SVGFEMorphologyElement>;
|
||||
// feOffset: preact.SVGAttributes<SVGFEOffsetElement>;
|
||||
// fePointLight: preact.SVGAttributes<SVGFEPointLightElement>;
|
||||
// feSpecularLighting: preact.SVGAttributes<SVGFESpecularLightingElement>;
|
||||
// feSpotLight: preact.SVGAttributes<SVGFESpotLightElement>;
|
||||
// feTile: preact.SVGAttributes<SVGFETileElement>;
|
||||
// feTurbulence: preact.SVGAttributes<SVGFETurbulenceElement>;
|
||||
// filter: preact.SVGAttributes<SVGFilterElement>;
|
||||
// foreignObject: preact.SVGAttributes<SVGForeignObjectElement>;
|
||||
// g: preact.SVGAttributes<SVGGElement>;
|
||||
// image: preact.SVGAttributes<SVGImageElement>;
|
||||
// line: preact.SVGAttributes<SVGLineElement>;
|
||||
// linearGradient: preact.SVGAttributes<SVGLinearGradientElement>;
|
||||
// marker: preact.SVGAttributes<SVGMarkerElement>;
|
||||
// mask: preact.SVGAttributes<SVGMaskElement>;
|
||||
// metadata: preact.SVGAttributes<SVGMetadataElement>;
|
||||
// mpath: preact.SVGAttributes<SVGMPathElement>;
|
||||
// path: preact.SVGAttributes<SVGPathElement>;
|
||||
// pattern: preact.SVGAttributes<SVGPatternElement>;
|
||||
// polygon: preact.SVGAttributes<SVGPolygonElement>;
|
||||
// polyline: preact.SVGAttributes<SVGPolylineElement>;
|
||||
// radialGradient: preact.SVGAttributes<SVGRadialGradientElement>;
|
||||
// rect: preact.SVGAttributes<SVGRectElement>;
|
||||
// set: preact.SVGAttributes<SVGSetElement>;
|
||||
// stop: preact.SVGAttributes<SVGStopElement>;
|
||||
// switch: preact.SVGAttributes<SVGSwitchElement>;
|
||||
// symbol: preact.SVGAttributes<SVGSymbolElement>;
|
||||
// text: preact.SVGAttributes<SVGTextElement>;
|
||||
// textPath: preact.SVGAttributes<SVGTextPathElement>;
|
||||
// tspan: preact.SVGAttributes<SVGTSpanElement>;
|
||||
// use: preact.SVGAttributes<SVGUseElement>;
|
||||
// view: preact.SVGAttributes<SVGViewElement>;
|
||||
// }
|
||||
|
||||
// export interface IntrinsicMathMLElements {
|
||||
// "annotation": preact.AnnotationMathMLAttributes<MathMLElement>;
|
||||
// "annotation-xml": preact.AnnotationXmlMathMLAttributes<MathMLElement>;
|
||||
// /** @deprecated See https://developer.mozilla.org/en-US/docs/Web/MathML/Element/maction */
|
||||
// "maction": preact.MActionMathMLAttributes<MathMLElement>;
|
||||
// "math": preact.MathMathMLAttributes<MathMLElement>;
|
||||
// /** This feature is non-standard. See https://developer.mozilla.org/en-US/docs/Web/MathML/Element/menclose */
|
||||
// "menclose": preact.MEncloseMathMLAttributes<MathMLElement>;
|
||||
// "merror": preact.MErrorMathMLAttributes<MathMLElement>;
|
||||
// /** @deprecated See https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mfenced */
|
||||
// "mfenced": preact.MFencedMathMLAttributes<MathMLElement>;
|
||||
// "mfrac": preact.MFracMathMLAttributes<MathMLElement>;
|
||||
// "mi": preact.MiMathMLAttributes<MathMLElement>;
|
||||
// "mmultiscripts": preact.MmultiScriptsMathMLAttributes<MathMLElement>;
|
||||
// "mn": preact.MNMathMLAttributes<MathMLElement>;
|
||||
// "mo": preact.MOMathMLAttributes<MathMLElement>;
|
||||
// "mover": preact.MOverMathMLAttributes<MathMLElement>;
|
||||
// "mpadded": preact.MPaddedMathMLAttributes<MathMLElement>;
|
||||
// "mphantom": preact.MPhantomMathMLAttributes<MathMLElement>;
|
||||
// "mprescripts": preact.MPrescriptsMathMLAttributes<MathMLElement>;
|
||||
// "mroot": preact.MRootMathMLAttributes<MathMLElement>;
|
||||
// "mrow": preact.MRowMathMLAttributes<MathMLElement>;
|
||||
// "ms": preact.MSMathMLAttributes<MathMLElement>;
|
||||
// "mspace": preact.MSpaceMathMLAttributes<MathMLElement>;
|
||||
// "msqrt": preact.MSqrtMathMLAttributes<MathMLElement>;
|
||||
// "mstyle": preact.MStyleMathMLAttributes<MathMLElement>;
|
||||
// "msub": preact.MSubMathMLAttributes<MathMLElement>;
|
||||
// "msubsup": preact.MSubsupMathMLAttributes<MathMLElement>;
|
||||
// "msup": preact.MSupMathMLAttributes<MathMLElement>;
|
||||
// "mtable": preact.MTableMathMLAttributes<MathMLElement>;
|
||||
// "mtd": preact.MTdMathMLAttributes<MathMLElement>;
|
||||
// "mtext": preact.MTextMathMLAttributes<MathMLElement>;
|
||||
// "mtr": preact.MTrMathMLAttributes<MathMLElement>;
|
||||
// "munder": preact.MUnderMathMLAttributes<MathMLElement>;
|
||||
// "munderover": preact.MUnderMathMLAttributes<MathMLElement>;
|
||||
// "semantics": preact.SemanticsMathMLAttributes<MathMLElement>;
|
||||
// }
|
||||
|
||||
// export interface IntrinsicHTMLElements {
|
||||
// a: preact.AccessibleAnchorHTMLAttributes<HTMLAnchorElement>;
|
||||
// abbr: preact.HTMLAttributes<HTMLElement>;
|
||||
// address: preact.HTMLAttributes<HTMLElement>;
|
||||
// area: preact.AccessibleAreaHTMLAttributes<HTMLAreaElement>;
|
||||
// article: preact.ArticleHTMLAttributes<HTMLElement>;
|
||||
// aside: preact.AsideHTMLAttributes<HTMLElement>;
|
||||
// audio: preact.AudioHTMLAttributes<HTMLAudioElement>;
|
||||
// b: preact.HTMLAttributes<HTMLElement>;
|
||||
// base: preact.BaseHTMLAttributes<HTMLBaseElement>;
|
||||
// bdi: preact.HTMLAttributes<HTMLElement>;
|
||||
// bdo: preact.HTMLAttributes<HTMLElement>;
|
||||
// big: preact.HTMLAttributes<HTMLElement>;
|
||||
// blockquote: preact.BlockquoteHTMLAttributes<HTMLQuoteElement>;
|
||||
// body: preact.HTMLAttributes<HTMLBodyElement>;
|
||||
// br: preact.BrHTMLAttributes<HTMLBRElement>;
|
||||
// button: preact.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
// canvas: preact.CanvasHTMLAttributes<HTMLCanvasElement>;
|
||||
// caption: preact.CaptionHTMLAttributes<HTMLTableCaptionElement>;
|
||||
// cite: preact.HTMLAttributes<HTMLElement>;
|
||||
// code: preact.HTMLAttributes<HTMLElement>;
|
||||
// col: preact.ColHTMLAttributes<HTMLTableColElement>;
|
||||
// colgroup: preact.ColgroupHTMLAttributes<HTMLTableColElement>;
|
||||
// data: preact.DataHTMLAttributes<HTMLDataElement>;
|
||||
// datalist: preact.DataListHTMLAttributes<HTMLDataListElement>;
|
||||
// dd: preact.DdHTMLAttributes<HTMLElement>;
|
||||
// del: preact.DelHTMLAttributes<HTMLModElement>;
|
||||
// details: preact.DetailsHTMLAttributes<HTMLDetailsElement>;
|
||||
// dfn: preact.HTMLAttributes<HTMLElement>;
|
||||
// dialog: preact.DialogHTMLAttributes<HTMLDialogElement>;
|
||||
// div: preact.HTMLAttributes<HTMLDivElement>;
|
||||
// dl: preact.DlHTMLAttributes<HTMLDListElement>;
|
||||
// dt: preact.DtHTMLAttributes<HTMLElement>;
|
||||
// em: preact.HTMLAttributes<HTMLElement>;
|
||||
// embed: preact.EmbedHTMLAttributes<HTMLEmbedElement>;
|
||||
// fieldset: preact.FieldsetHTMLAttributes<HTMLFieldSetElement>;
|
||||
// figcaption: preact.FigcaptionHTMLAttributes<HTMLElement>;
|
||||
// figure: preact.HTMLAttributes<HTMLElement>;
|
||||
// footer: preact.FooterHTMLAttributes<HTMLElement>;
|
||||
// form: preact.FormHTMLAttributes<HTMLFormElement>;
|
||||
// h1: preact.HeadingHTMLAttributes<HTMLHeadingElement>;
|
||||
// h2: preact.HeadingHTMLAttributes<HTMLHeadingElement>;
|
||||
// h3: preact.HeadingHTMLAttributes<HTMLHeadingElement>;
|
||||
// h4: preact.HeadingHTMLAttributes<HTMLHeadingElement>;
|
||||
// h5: preact.HeadingHTMLAttributes<HTMLHeadingElement>;
|
||||
// h6: preact.HeadingHTMLAttributes<HTMLHeadingElement>;
|
||||
// head: preact.HeadHTMLAttributes<HTMLHeadElement>;
|
||||
// header: preact.HeaderHTMLAttributes<HTMLElement>;
|
||||
// hgroup: preact.HTMLAttributes<HTMLElement>;
|
||||
// hr: preact.HrHTMLAttributes<HTMLHRElement>;
|
||||
// html: preact.HtmlHTMLAttributes<HTMLHtmlElement>;
|
||||
// i: preact.HTMLAttributes<HTMLElement>;
|
||||
// iframe: preact.IframeHTMLAttributes<HTMLIFrameElement>;
|
||||
// img: preact.AccessibleImgHTMLAttributes<HTMLImageElement>;
|
||||
// input: preact.AccessibleInputHTMLAttributes<HTMLInputElement>;
|
||||
// ins: preact.InsHTMLAttributes<HTMLModElement>;
|
||||
// kbd: preact.HTMLAttributes<HTMLElement>;
|
||||
// keygen: preact.KeygenHTMLAttributes<HTMLUnknownElement>;
|
||||
// label: preact.LabelHTMLAttributes<HTMLLabelElement>;
|
||||
// legend: preact.LegendHTMLAttributes<HTMLLegendElement>;
|
||||
// li: preact.LiHTMLAttributes<HTMLLIElement>;
|
||||
// link: preact.LinkHTMLAttributes<HTMLLinkElement>;
|
||||
// main: preact.MainHTMLAttributes<HTMLElement>;
|
||||
// map: preact.MapHTMLAttributes<HTMLMapElement>;
|
||||
// mark: preact.HTMLAttributes<HTMLElement>;
|
||||
// marquee: preact.MarqueeHTMLAttributes<HTMLMarqueeElement>;
|
||||
// menu: preact.MenuHTMLAttributes<HTMLMenuElement>;
|
||||
// menuitem: preact.HTMLAttributes<HTMLUnknownElement>;
|
||||
// meta: preact.MetaHTMLAttributes<HTMLMetaElement>;
|
||||
// meter: preact.MeterHTMLAttributes<HTMLMeterElement>;
|
||||
// nav: preact.NavHTMLAttributes<HTMLElement>;
|
||||
// noscript: preact.NoScriptHTMLAttributes<HTMLElement>;
|
||||
// object: preact.ObjectHTMLAttributes<HTMLObjectElement>;
|
||||
// ol: preact.OlHTMLAttributes<HTMLOListElement>;
|
||||
// optgroup: preact.OptgroupHTMLAttributes<HTMLOptGroupElement>;
|
||||
// option: preact.OptionHTMLAttributes<HTMLOptionElement>;
|
||||
// output: preact.OutputHTMLAttributes<HTMLOutputElement>;
|
||||
// p: preact.HTMLAttributes<HTMLParagraphElement>;
|
||||
// param: preact.ParamHTMLAttributes<HTMLParamElement>;
|
||||
// picture: preact.PictureHTMLAttributes<HTMLPictureElement>;
|
||||
// pre: preact.HTMLAttributes<HTMLPreElement>;
|
||||
// progress: preact.ProgressHTMLAttributes<HTMLProgressElement>;
|
||||
// q: preact.QuoteHTMLAttributes<HTMLQuoteElement>;
|
||||
// rp: preact.HTMLAttributes<HTMLElement>;
|
||||
// rt: preact.HTMLAttributes<HTMLElement>;
|
||||
// ruby: preact.HTMLAttributes<HTMLElement>;
|
||||
// s: preact.HTMLAttributes<HTMLElement>;
|
||||
// samp: preact.HTMLAttributes<HTMLElement>;
|
||||
// script: preact.ScriptHTMLAttributes<HTMLScriptElement>;
|
||||
// search: preact.SearchHTMLAttributes<HTMLElement>;
|
||||
// section: preact.HTMLAttributes<HTMLElement>;
|
||||
// select: preact.AccessibleSelectHTMLAttributes<HTMLSelectElement>;
|
||||
// slot: preact.SlotHTMLAttributes<HTMLSlotElement>;
|
||||
// small: preact.HTMLAttributes<HTMLElement>;
|
||||
// source: preact.SourceHTMLAttributes<HTMLSourceElement>;
|
||||
// span: preact.HTMLAttributes<HTMLSpanElement>;
|
||||
// strong: preact.HTMLAttributes<HTMLElement>;
|
||||
// style: preact.StyleHTMLAttributes<HTMLStyleElement>;
|
||||
// sub: preact.HTMLAttributes<HTMLElement>;
|
||||
// summary: preact.HTMLAttributes<HTMLElement>;
|
||||
// sup: preact.HTMLAttributes<HTMLElement>;
|
||||
// table: preact.TableHTMLAttributes<HTMLTableElement>;
|
||||
// tbody: preact.HTMLAttributes<HTMLTableSectionElement>;
|
||||
// td: preact.TdHTMLAttributes<HTMLTableCellElement>;
|
||||
// template: preact.TemplateHTMLAttributes<HTMLTemplateElement>;
|
||||
// textarea: preact.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
||||
// tfoot: preact.HTMLAttributes<HTMLTableSectionElement>;
|
||||
// th: preact.ThHTMLAttributes<HTMLTableCellElement>;
|
||||
// thead: preact.HTMLAttributes<HTMLTableSectionElement>;
|
||||
// time: preact.TimeHTMLAttributes<HTMLTimeElement>;
|
||||
// title: preact.TitleHTMLAttributes<HTMLTitleElement>;
|
||||
// tr: preact.HTMLAttributes<HTMLTableRowElement>;
|
||||
// track: preact.TrackHTMLAttributes<HTMLTrackElement>;
|
||||
// u: preact.UlHTMLAttributes<HTMLElement>;
|
||||
// ul: preact.HTMLAttributes<HTMLUListElement>;
|
||||
// var: preact.HTMLAttributes<HTMLElement>;
|
||||
// video: preact.VideoHTMLAttributes<HTMLVideoElement>;
|
||||
// wbr: preact.WbrHTMLAttributes<HTMLElement>;
|
||||
// }
|
||||
|
||||
// export interface IntrinsicElements
|
||||
// extends IntrinsicSVGElements,
|
||||
// IntrinsicMathMLElements,
|
||||
// IntrinsicHTMLElements {}
|
||||
// }
|
||||
12
packages/lit-jsx/vite.config.ts
Normal file
12
packages/lit-jsx/vite.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/// <reference types="vitest/config" />
|
||||
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
name: "unit",
|
||||
environment: "node",
|
||||
dir: "./test",
|
||||
include: ["./**/*.test.{ts,tsx}"],
|
||||
},
|
||||
});
|
||||
@@ -17,7 +17,7 @@ COPY web .
|
||||
RUN npm run build-proxy
|
||||
|
||||
# Stage 2: Build
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25-bookworm AS builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "authentik"
|
||||
version = "2025.8.1"
|
||||
version = "2025.10.0-rc1"
|
||||
description = ""
|
||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||
requires-python = "==3.13.*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Stage 1: Build
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25-bookworm AS builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Stage 1: Build
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25-bookworm AS builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2025.8.1
|
||||
version: 2025.10.0-rc1
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
|
||||
@@ -10,6 +10,7 @@ from docker.types.healthcheck import Healthcheck
|
||||
|
||||
from authentik.core.tests.utils import create_test_flow
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.outposts.models import (
|
||||
DockerServiceConnection,
|
||||
Outpost,
|
||||
@@ -88,6 +89,7 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):
|
||||
pass
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@CONFIG.patch("outposts.container_image_base", "ghcr.io/goauthentik/dev-proxy:gh-main")
|
||||
def test_docker_controller(self):
|
||||
"""test that deployment requires update"""
|
||||
controller = DockerController(self.outpost, self.service_connection)
|
||||
|
||||
8
uv.lock
generated
8
uv.lock
generated
@@ -159,7 +159,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "authentik"
|
||||
version = "2025.8.1"
|
||||
version = "2025.10.0rc1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "argon2-cffi" },
|
||||
@@ -2736,11 +2736,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "6.2.0"
|
||||
version = "6.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/9a/0551e01ba52b944f97480721656578c8a7c46b51b99d66814f85fe3a4f3e/redis-6.2.0.tar.gz", hash = "sha256:e821f129b75dde6cb99dd35e5c76e8c49512a5a0d8dfdc560b2fbd44b85ca977", size = 4639129, upload-time = "2025-05-28T05:01:18.91Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/cd/030274634a1a052b708756016283ea3d84e91ae45f74d7f5dcf55d753a0f/redis-6.3.0.tar.gz", hash = "sha256:3000dbe532babfb0999cdab7b3e5744bcb23e51923febcfaeb52c8cfb29632ef", size = 4647275, upload-time = "2025-08-05T08:12:31.648Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/67/e60968d3b0e077495a8fee89cf3f2373db98e528288a48f1ee44967f6e8c/redis-6.2.0-py3-none-any.whl", hash = "sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e", size = 278659, upload-time = "2025-05-28T05:01:16.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/a7/2fe45801534a187543fc45d28b3844d84559c1589255bc2ece30d92dc205/redis-6.3.0-py3-none-any.whl", hash = "sha256:92f079d656ded871535e099080f70fab8e75273c0236797126ac60242d638e9b", size = 280018, upload-time = "2025-08-05T08:12:30.093Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
1560
web/package-lock.json
generated
1560
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@goauthentik/web",
|
||||
"version": "2025.8.1",
|
||||
"version": "2025.10.0-rc1",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -98,6 +98,7 @@
|
||||
"@goauthentik/core": "^1.0.0",
|
||||
"@goauthentik/esbuild-plugin-live-reload": "^1.1.0",
|
||||
"@goauthentik/eslint-config": "^1.0.5",
|
||||
"@goauthentik/lit-jsx": "^1.0.0",
|
||||
"@goauthentik/prettier-config": "^3.1.0",
|
||||
"@goauthentik/tsconfig": "^1.0.4",
|
||||
"@hcaptcha/types": "^1.0.4",
|
||||
@@ -113,18 +114,18 @@
|
||||
"@openlayers-elements/maps": "^0.4.0",
|
||||
"@patternfly/elements": "^4.1.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^10.3.0",
|
||||
"@sentry/browser": "^10.5.0",
|
||||
"@spotlightjs/spotlight": "^3.0.2",
|
||||
"@storybook/addon-docs": "^9.1.1",
|
||||
"@storybook/addon-links": "^9.1.1",
|
||||
"@storybook/web-components": "^9.1.1",
|
||||
"@storybook/web-components-vite": "^9.1.1",
|
||||
"@storybook/addon-docs": "^9.1.2",
|
||||
"@storybook/addon-links": "^9.1.2",
|
||||
"@storybook/web-components": "^9.1.2",
|
||||
"@storybook/web-components-vite": "^9.1.2",
|
||||
"@types/codemirror": "^5.60.16",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/guacamole-common-js": "^1.5.3",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^24.2.1",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
||||
"@typescript-eslint/parser": "^8.38.0",
|
||||
@@ -135,12 +136,12 @@
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"construct-style-sheets-polyfill": "^3.1.0",
|
||||
"core-js": "^3.44.0",
|
||||
"core-js": "^3.45.0",
|
||||
"country-flag-icons": "^1.5.19",
|
||||
"date-fns": "^4.1.0",
|
||||
"deepmerge-ts": "^7.1.5",
|
||||
"dompurify": "^3.2.6",
|
||||
"esbuild": "^0.25.8",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild-plugin-copy": "^2.1.1",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"esbuild-plugins-node-modules-polyfill": "^1.7.1",
|
||||
@@ -177,7 +178,7 @@
|
||||
"ts-pattern": "^5.8.0",
|
||||
"turnstile-types": "^1.2.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.39.0",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"webcomponent-qr-code": "^1.3.0",
|
||||
"wireit": "^0.14.12",
|
||||
@@ -190,11 +191,11 @@
|
||||
"@rollup/rollup-darwin-arm64": "^4.46.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "^4.46.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.46.2",
|
||||
"@wdio/browser-runner": "^9.19.0",
|
||||
"@wdio/cli": "^9.19.0",
|
||||
"@wdio/spec-reporter": "^9.19.0",
|
||||
"@wdio/browser-runner": "^9.19.1",
|
||||
"@wdio/cli": "^9.19.1",
|
||||
"@wdio/spec-reporter": "^9.19.1",
|
||||
"@web/test-runner": "^0.20.2",
|
||||
"chromedriver": "^139.0.0"
|
||||
"chromedriver": "^139.0.1"
|
||||
},
|
||||
"wireit": {
|
||||
"build": {
|
||||
|
||||
@@ -66,7 +66,7 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
||||
quickActions: QuickAction[] = [
|
||||
[msg("Create a new application"), paramURL("/core/applications", { createWizard: true })],
|
||||
[msg("Check the logs"), paramURL("/events/log")],
|
||||
[msg("Explore integrations"), "https://integrations.goauthentik.io/", true],
|
||||
[msg("Explore integrations"), "https://goauthentik.io/integrations/", true],
|
||||
[msg("Manage users"), paramURL("/identity/users")],
|
||||
[
|
||||
msg("Check the release notes"),
|
||||
|
||||
@@ -51,8 +51,6 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
|
||||
}
|
||||
|
||||
async send(settingsRequest: SettingsRequest): Promise<Settings> {
|
||||
settingsRequest.flags ??= this.settings.flags;
|
||||
|
||||
const result = await new AdminApi(DEFAULT_CONFIG).adminSettingsUpdate({
|
||||
settingsRequest,
|
||||
});
|
||||
@@ -254,7 +252,7 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
|
||||
<ak-form-element-horizontal label=${msg("Flags")} name="flags" required>
|
||||
<ak-codemirror
|
||||
mode=${CodeMirrorMode.YAML}
|
||||
value="${YAML.stringify(settings.flags ?? {})}"
|
||||
value="${YAML.stringify(settings?.flags ?? {})}"
|
||||
>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
|
||||
@@ -530,8 +530,9 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label=${msg("Advanced settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Policy engine mode")}
|
||||
required
|
||||
|
||||
@@ -414,8 +414,9 @@ export class PlexSourceForm extends WithCapabilitiesConfig(BaseSourceForm<PlexSo
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label=${msg("Advanced settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Policy engine mode")}
|
||||
required
|
||||
|
||||
@@ -574,8 +574,9 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group label=${msg("Advanced settings")}>
|
||||
<div class="pf-c-form">
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${msg("Advanced settings")} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Policy engine mode")}
|
||||
required
|
||||
|
||||
@@ -20,22 +20,6 @@ export const EscapeTrustPolicy = trustedTypes.createPolicy("authentik-escape", {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Trusted types policy that removes all HTML content.
|
||||
*
|
||||
*
|
||||
* @returns {TrustedHTML} All remaining text content.
|
||||
*/
|
||||
export const StripHTMLTrustPolicy = trustedTypes.createPolicy("authentik-strip-html", {
|
||||
createHTML: (untrustedHTML: string) => {
|
||||
return DOMPurify.sanitize(untrustedHTML, {
|
||||
RETURN_TRUSTED_TYPE: false,
|
||||
ALLOWED_TAGS: [],
|
||||
ALLOWED_ATTR: [],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Trusted types policy, stripping all HTML content.
|
||||
*
|
||||
@@ -121,9 +105,7 @@ export function renderStaticHTMLUnsafe(untrustedHTML: unknown): string {
|
||||
|
||||
render(untrustedHTML, container);
|
||||
|
||||
const result = container.innerHTML
|
||||
// Remove all comments as they can interfere with the styles.
|
||||
.replaceAll("<!---->", "")
|
||||
.replaceAll(/<!--\?lit\$\d+\$-->/g, "");
|
||||
const result = container.innerHTML;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import "#elements/buttons/Dropdown";
|
||||
|
||||
import { StripHTMLTrustPolicy } from "#common/purify";
|
||||
import { rootInterface } from "#common/theme";
|
||||
|
||||
import { FormAssociated, FormAssociatedElement } from "#elements/forms/form-associated-element";
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { PaginatedResponse } from "#elements/table/Table";
|
||||
|
||||
import DjangoQL, { Introspections } from "@mrmarble/djangoql-completion";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, CSSResult, html, LitElement, nothing, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { css, CSSResult, html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { createRef, ref, Ref } from "lit/directives/ref.js";
|
||||
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFSearchInput from "@patternfly/patternfly/components/SearchInput/search-input.css";
|
||||
@@ -29,26 +25,40 @@ export class QL extends DjangoQL {
|
||||
textareaResize() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array or length, return logical index of the element at the given delta.
|
||||
* This is effectively a modulo loop, allowing for positive and negative deltas.
|
||||
*/
|
||||
function torusIndex(lengthLike: number | ArrayLike<number>, delta: number): number {
|
||||
const length = typeof lengthLike === "number" ? lengthLike : lengthLike.length;
|
||||
@customElement("ak-search-ql")
|
||||
export class QLSearch extends AKElement {
|
||||
@property()
|
||||
value?: string;
|
||||
|
||||
if (delta < 0) {
|
||||
return (length + delta) % length;
|
||||
@query("[name=search]")
|
||||
searchElement?: HTMLTextAreaElement;
|
||||
|
||||
@state()
|
||||
menuOpen = false;
|
||||
|
||||
@property()
|
||||
onSearch?: (value: string) => void;
|
||||
|
||||
@state()
|
||||
selected?: number;
|
||||
|
||||
@state()
|
||||
cursorX: number = 0;
|
||||
|
||||
@state()
|
||||
cursorY: number = 0;
|
||||
|
||||
ql?: QL;
|
||||
canvas?: CanvasRenderingContext2D;
|
||||
|
||||
set apiResponse(value: PaginatedResponse<unknown> | undefined) {
|
||||
if (!value || !value.autocomplete || !this.ql) {
|
||||
return;
|
||||
}
|
||||
this.ql.loadIntrospections(value.autocomplete as unknown as Introspections);
|
||||
}
|
||||
|
||||
return ((delta % length) + length) % length;
|
||||
}
|
||||
|
||||
@customElement("ak-search-ql")
|
||||
export class QLSearch extends FormAssociatedElement<string> implements FormAssociated {
|
||||
declare anchorRef: Ref<HTMLTextAreaElement>;
|
||||
declare anchor: HTMLTextAreaElement | null;
|
||||
|
||||
public static styles: CSSResult[] = [
|
||||
static styles: CSSResult[] = [
|
||||
PFBase,
|
||||
PFFormControl,
|
||||
PFSearchInput,
|
||||
@@ -56,409 +66,207 @@ export class QLSearch extends FormAssociatedElement<string> implements FormAssoc
|
||||
::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ql.pf-c-form-control {
|
||||
--input-height: 2.25em;
|
||||
|
||||
height: var(--input-height);
|
||||
min-height: var(--input-height);
|
||||
max-height: calc(var(--input-height) * 6);
|
||||
font-family: monospace;
|
||||
resize: vertical;
|
||||
height: 2.25em;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--pf-c-search-input__menu-item--hover--BackgroundColor);
|
||||
}
|
||||
|
||||
:host([theme="dark"]) {
|
||||
.pf-c-search-input__menu {
|
||||
--pf-c-search-input__menu--BackgroundColor: var(--ak-dark-background-light-ish);
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
|
||||
.pf-c-search-input__menu-item {
|
||||
--pf-c-search-input__menu-item--Color: var(--ak-dark-foreground);
|
||||
}
|
||||
|
||||
.pf-c-search-input__menu-item:hover {
|
||||
--pf-c-search-input__menu-item--BackgroundColor: var(
|
||||
--ak-dark-background-lighter
|
||||
);
|
||||
}
|
||||
|
||||
.pf-c-search-input__menu-list-item.selected {
|
||||
--pf-c-search-input__menu-item--hover--BackgroundColor: var(
|
||||
--ak-dark-background-light
|
||||
);
|
||||
}
|
||||
|
||||
.pf-c-search-input__text::before {
|
||||
border: 0;
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__menu {
|
||||
--pf-c-search-input__menu--BackgroundColor: var(--ak-dark-background-light-ish);
|
||||
color: var(--ak-dark-foreground);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__menu-item {
|
||||
--pf-c-search-input__menu-item--Color: var(--ak-dark-foreground);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__menu-item:hover {
|
||||
--pf-c-search-input__menu-item--BackgroundColor: var(--ak-dark-background-lighter);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__menu-list-item.selected {
|
||||
--pf-c-search-input__menu-item--hover--BackgroundColor: var(
|
||||
--ak-dark-background-light
|
||||
);
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-search-input__text::before {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pf-c-search-input__menu {
|
||||
position: fixed;
|
||||
min-width: 0;
|
||||
overflow-y: auto;
|
||||
max-height: 50vh;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
//#region Properties
|
||||
|
||||
@property({ type: Boolean })
|
||||
public open = false;
|
||||
|
||||
@property({ type: Number, attribute: false })
|
||||
public selectionIndex = -1;
|
||||
|
||||
#value = "";
|
||||
|
||||
@property({ type: String })
|
||||
public get value(): string {
|
||||
return this.#value;
|
||||
}
|
||||
|
||||
public set value(value: unknown) {
|
||||
const parsed = typeof value === "string" ? value : "";
|
||||
|
||||
this.setFormValue(parsed.trim(), parsed);
|
||||
this.#value = parsed;
|
||||
|
||||
if (this.anchor) {
|
||||
this.anchor.value = this.#value;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region State
|
||||
|
||||
#menuRef = createRef<HTMLDivElement>();
|
||||
|
||||
#ql: QL | null = null;
|
||||
#ctx: OffscreenCanvasRenderingContext2D | null = null;
|
||||
#letterWidth = -1;
|
||||
#scrollContainer: HTMLElement | null = null;
|
||||
|
||||
public set apiResponse(value: PaginatedResponse<unknown> | undefined) {
|
||||
if (!value?.autocomplete || !this.#ql) {
|
||||
firstUpdated() {
|
||||
if (!this.searchElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#ql.loadIntrospections(value.autocomplete as unknown as Introspections);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
public override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.internals.ariaAutoComplete = "list";
|
||||
this.internals.role = "combobox";
|
||||
this.internals.ariaHasPopup = "listbox";
|
||||
|
||||
this.#scrollContainer =
|
||||
rootInterface<LitElement>().renderRoot.querySelector("#main-content");
|
||||
|
||||
this.#scrollContainer?.addEventListener("scroll", this.#updateDropdownPosition, {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
this.tabIndex = 0;
|
||||
}
|
||||
|
||||
public override disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
|
||||
this.#scrollContainer?.removeEventListener("scroll", this.#updateDropdownPosition);
|
||||
}
|
||||
|
||||
public formStateRestoreCallback(state: string) {
|
||||
this.value = state;
|
||||
}
|
||||
|
||||
public formResetCallback() {
|
||||
this.value = "";
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public override updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("open")) {
|
||||
this.internals.ariaExpanded = this.open ? "true" : "false";
|
||||
}
|
||||
|
||||
if (changedProperties.has("selectionIndex")) {
|
||||
const id = `suggestion-${this.selectionIndex}`;
|
||||
|
||||
this.setAttribute("aria-activedescendant", this.selectionIndex === -1 ? "" : id);
|
||||
|
||||
this.renderRoot.querySelector(`#${id}`)?.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "nearest",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public override firstUpdated() {
|
||||
const textarea = this.anchorRef.value;
|
||||
|
||||
if (!textarea) return;
|
||||
|
||||
this.#ql = new QL({
|
||||
this.ql = new QL({
|
||||
completionEnabled: true,
|
||||
introspections: {
|
||||
current_model: "",
|
||||
models: {},
|
||||
},
|
||||
selector: textarea,
|
||||
selector: this.searchElement,
|
||||
autoResize: false,
|
||||
});
|
||||
|
||||
const canvas = new OffscreenCanvas(300, 150);
|
||||
this.#ctx = canvas.getContext("2d");
|
||||
|
||||
if (!this.#ctx) {
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
console.error("authentik/ql: failed to get canvas context");
|
||||
return;
|
||||
}
|
||||
|
||||
this.#ctx.font = window.getComputedStyle(textarea).font;
|
||||
|
||||
// We need the width of a letter to measure x; we use a monospaced font but still
|
||||
// check the length for `m` as its the widest ASCII char
|
||||
const metrics = this.#ctx?.measureText("m");
|
||||
this.#letterWidth = Math.ceil(metrics?.width || 0) + 1;
|
||||
context.font = window.getComputedStyle(this.searchElement).font;
|
||||
this.canvas = context;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Completions
|
||||
|
||||
#refreshCompletions = () => {
|
||||
if (this.anchor) {
|
||||
this.value = this.anchor.value;
|
||||
}
|
||||
|
||||
if (!this.#ql) {
|
||||
refreshCompletions() {
|
||||
this.value = this.searchElement?.value;
|
||||
if (!this.ql) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#ql.generateSuggestions();
|
||||
|
||||
if (this.#ql.suggestions.length < 1 || this.#ql.loading) {
|
||||
this.open = false;
|
||||
this.ql.generateSuggestions();
|
||||
if (this.ql.suggestions.length < 1 || this.ql.loading) {
|
||||
this.menuOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.open = true;
|
||||
|
||||
this.menuOpen = true;
|
||||
this.updateDropdownPosition();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
requestAnimationFrame(this.#updateDropdownPosition);
|
||||
};
|
||||
|
||||
#updateDropdownPosition = () => {
|
||||
const anchor = this.anchorRef.value;
|
||||
const menu = this.#menuRef.value;
|
||||
|
||||
if (!anchor || !menu) return;
|
||||
|
||||
updateDropdownPosition() {
|
||||
if (!this.searchElement) {
|
||||
return;
|
||||
}
|
||||
const bcr = this.getBoundingClientRect();
|
||||
const style = window.getComputedStyle(anchor);
|
||||
// We need the width of a letter to measure x; we use a monospaced font but still
|
||||
// check the length for `m` as its the widest ASCII char
|
||||
const metrics = this.canvas?.measureText("m");
|
||||
const letterWidth = Math.ceil(metrics?.width || 0) + 1;
|
||||
|
||||
// Mostly static variables for padding, font line-height and how many
|
||||
const lineHeight = parseInt(style.lineHeight, 10);
|
||||
const paddingTop = parseInt(style.paddingTop, 10);
|
||||
const paddingLeft = parseInt(style.paddingLeft, 10);
|
||||
const paddingRight = parseInt(style.paddingRight, 10);
|
||||
|
||||
const lineHeight = parseInt(window.getComputedStyle(this.searchElement).lineHeight, 10);
|
||||
const paddingTop = parseInt(window.getComputedStyle(this.searchElement).paddingTop, 10);
|
||||
const paddingLeft = parseInt(window.getComputedStyle(this.searchElement).paddingLeft, 10);
|
||||
const paddingRight = parseInt(window.getComputedStyle(this.searchElement).paddingRight, 10);
|
||||
const actualInnerWidth = bcr.width - paddingLeft - paddingRight;
|
||||
|
||||
let relX = 0;
|
||||
let relY = 1;
|
||||
let letterIndex = 0;
|
||||
|
||||
for (const word of anchor.value.split(" ")) {
|
||||
this.searchElement.value.split(" ").some((word, idx) => {
|
||||
letterIndex += word.length;
|
||||
const newRelX = relX + word.length * this.#letterWidth;
|
||||
|
||||
const newRelX = relX + word.length * letterWidth;
|
||||
if (newRelX > actualInnerWidth) {
|
||||
relY += 1;
|
||||
|
||||
if (letterIndex > anchor.selectionStart) {
|
||||
if (letterIndex > this.searchElement!.selectionStart) {
|
||||
relX =
|
||||
this.#letterWidth * word.length -
|
||||
(letterIndex - anchor.selectionStart) * this.#letterWidth;
|
||||
|
||||
break;
|
||||
letterWidth * word.length -
|
||||
(letterIndex - this.searchElement!.selectionStart) * letterWidth;
|
||||
return true;
|
||||
}
|
||||
|
||||
relX = word.length * this.#letterWidth;
|
||||
relX = word.length * letterWidth;
|
||||
} else {
|
||||
relX = newRelX + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const x = bcr.x + paddingLeft + relX;
|
||||
const y = bcr.y + paddingTop + relY * lineHeight;
|
||||
|
||||
Object.assign(menu.style, {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
} satisfies Partial<CSSStyleDeclaration>);
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Event Listeners
|
||||
|
||||
#keydownListener = (event: KeyboardEvent) => {
|
||||
this.#updateDropdownPosition();
|
||||
|
||||
const suggestionsLength = this.#ql?.suggestions.length;
|
||||
|
||||
if (event.key === "Enter" && !this.open && this.form) {
|
||||
const submitEvent = new SubmitEvent("submit", {
|
||||
submitter: this,
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
});
|
||||
|
||||
this.form.dispatchEvent(submitEvent);
|
||||
this.cursorX = bcr.x + paddingLeft + relX;
|
||||
this.cursorY = bcr.y + paddingTop + relY * lineHeight;
|
||||
}
|
||||
|
||||
onKeyDown(ev: KeyboardEvent) {
|
||||
this.updateDropdownPosition();
|
||||
if (ev.key === "Enter" && ev.metaKey && this.onSearch && this.searchElement) {
|
||||
this.onSearch(this.searchElement?.value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.open && suggestionsLength) {
|
||||
if (this.selectionIndex === -1) {
|
||||
this.selectionIndex = 0;
|
||||
} else {
|
||||
this.selectionIndex = torusIndex(suggestionsLength, this.selectionIndex + 1);
|
||||
}
|
||||
|
||||
this.#refreshCompletions();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectionIndex = 0;
|
||||
this.#refreshCompletions();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.open) return;
|
||||
|
||||
switch (event.key) {
|
||||
if (!this.menuOpen) return;
|
||||
switch (ev.key) {
|
||||
case "ArrowUp":
|
||||
if (suggestionsLength) {
|
||||
if (this.selectionIndex === -1) {
|
||||
this.selectionIndex = suggestionsLength - 1;
|
||||
if (this.ql?.suggestions.length) {
|
||||
if (this.selected === undefined) {
|
||||
this.selected = this.ql?.suggestions.length - 1;
|
||||
} else if (this.selected === 0) {
|
||||
this.selected = undefined;
|
||||
} else {
|
||||
this.selectionIndex = torusIndex(
|
||||
suggestionsLength,
|
||||
this.selectionIndex - 1,
|
||||
);
|
||||
this.selected -= 1;
|
||||
}
|
||||
|
||||
this.#refreshCompletions();
|
||||
event.preventDefault();
|
||||
this.refreshCompletions();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (this.ql?.suggestions.length) {
|
||||
if (this.selected === undefined) {
|
||||
this.selected = 0;
|
||||
} else if (this.selected < this.ql?.suggestions.length - 1) {
|
||||
this.selected += 1;
|
||||
} else {
|
||||
this.selected = undefined;
|
||||
}
|
||||
this.refreshCompletions();
|
||||
ev.preventDefault();
|
||||
}
|
||||
break;
|
||||
case "Tab":
|
||||
if (this.selectionIndex) {
|
||||
this.#ql?.selectCompletion(this.selectionIndex);
|
||||
event.preventDefault();
|
||||
if (this.selected) {
|
||||
this.ql?.selectCompletion(this.selected);
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
return;
|
||||
break;
|
||||
case "Enter":
|
||||
// Technically this is a textarea, due to automatic multi-line feature,
|
||||
// but other than that it should look and behave like a normal input.
|
||||
// So expected behavior when pressing Enter is to submit the form,
|
||||
// not to add a new line.
|
||||
if (this.selectionIndex !== -1) {
|
||||
this.#ql?.selectCompletion(this.selectionIndex);
|
||||
this.selectionIndex = 0;
|
||||
if (this.selected !== undefined) {
|
||||
this.ql?.selectCompletion(this.selected);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
return;
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case "Escape":
|
||||
this.open = false;
|
||||
return;
|
||||
this.menuOpen = false;
|
||||
break;
|
||||
case "Shift": // Shift
|
||||
case "Control": // Ctrl
|
||||
case "Alt": // Alt
|
||||
case "Meta": // Windows Key or Cmd on Mac
|
||||
// Control keys shouldn't trigger completion popup
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#blurListener = ({ relatedTarget }: FocusEvent) => {
|
||||
if (relatedTarget instanceof Node && this.renderRoot.contains(relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.open = false;
|
||||
};
|
||||
|
||||
#focusListener = () => {
|
||||
this.selectionIndex = this.selectionIndex === -1 ? 0 : this.selectionIndex;
|
||||
|
||||
this.#refreshCompletions();
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Render
|
||||
|
||||
protected renderMenu() {
|
||||
if (!this.open || !this.#ql) {
|
||||
renderMenu() {
|
||||
if (!this.menuOpen || !this.ql) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div ${ref(this.#menuRef)} class="pf-c-search-input__menu">
|
||||
<ul
|
||||
class="pf-c-search-input__menu-list"
|
||||
role="listbox"
|
||||
id="ql-suggestions"
|
||||
aria-label=${msg("Query suggestions")}
|
||||
>
|
||||
${this.#ql.suggestions.map((suggestion, idx) => {
|
||||
// Cast to string to sooth Lit Analyzer's primitive type rule.
|
||||
const label = `${StripHTMLTrustPolicy.createHTML(suggestion.suggestionText)}`;
|
||||
|
||||
<div
|
||||
class="pf-c-search-input__menu"
|
||||
style="left: ${this.cursorX}px; top: ${this.cursorY}px;"
|
||||
>
|
||||
<ul class="pf-c-search-input__menu-list">
|
||||
${this.ql.suggestions.map((suggestion, idx) => {
|
||||
return html`<li
|
||||
role="option"
|
||||
id="suggestion-${idx}"
|
||||
aria-selected=${this.selectionIndex === idx ? "true" : "false"}
|
||||
class="pf-c-search-input__menu-list-item ${this.selectionIndex === idx
|
||||
class="pf-c-search-input__menu-list-item ${this.selected === idx
|
||||
? "selected"
|
||||
: ""}"
|
||||
>
|
||||
<button
|
||||
class="pf-c-search-input__menu-item"
|
||||
type="button"
|
||||
aria-label=${label}
|
||||
@click=${() => {
|
||||
this.#ql?.selectCompletion(idx);
|
||||
this.#refreshCompletions();
|
||||
this.ql?.selectCompletion(idx);
|
||||
this.refreshCompletions();
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-search-input__menu-item-text pf-m-monospace">
|
||||
${suggestion.text}</span
|
||||
<span class="pf-c-search-input__menu-item-text"
|
||||
>${suggestion.text}</span
|
||||
>
|
||||
</button>
|
||||
</li>`;
|
||||
@@ -468,33 +276,25 @@ export class QLSearch extends FormAssociatedElement<string> implements FormAssoc
|
||||
`;
|
||||
}
|
||||
|
||||
public override render(): TemplateResult {
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-search-input">
|
||||
<div class="pf-c-search-input__bar">
|
||||
<span class="pf-c-search-input__text">
|
||||
<textarea
|
||||
${ref(this.anchorRef)}
|
||||
class="pf-c-form-control pf-m-monospace ql"
|
||||
class="pf-c-form-control ql"
|
||||
name="search"
|
||||
autocomplete="off"
|
||||
aria-controls="ql-suggestions"
|
||||
?required=${this.required}
|
||||
placeholder=${msg("Search...")}
|
||||
spellcheck="false"
|
||||
@input=${this.#refreshCompletions}
|
||||
@focus=${this.#focusListener}
|
||||
@blur=${this.#blurListener}
|
||||
@keydown=${this.#keydownListener}
|
||||
@input=${(ev: InputEvent) => this.refreshCompletions()}
|
||||
@keydown=${this.onKeyDown}
|
||||
>
|
||||
${ifDefined(this.#value)}</textarea
|
||||
${ifDefined(this.value)}</textarea
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
${this.renderMenu()}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -21,6 +21,7 @@ import diffGrammar from "highlight.js/lib/languages/diff";
|
||||
import confGrammar from "highlight.js/lib/languages/ini";
|
||||
import nginxGrammar from "highlight.js/lib/languages/nginx";
|
||||
import { common } from "lowlight";
|
||||
import React from "react";
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
import * as runtime from "react/jsx-runtime";
|
||||
import rehypeHighlight, { Options as HighlightOptions } from "rehype-highlight";
|
||||
@@ -225,15 +226,17 @@ export class AKMDX extends AKElement {
|
||||
const { frontmatter = {} } = mdxExports;
|
||||
|
||||
this.#reactRoot.render(
|
||||
<MDXModuleContext.Provider value={mdxModule}>
|
||||
<Content
|
||||
frontmatter={frontmatter}
|
||||
components={{
|
||||
React.createElement(
|
||||
MDXModuleContext.Provider,
|
||||
{ value: mdxModule },
|
||||
React.createElement(Content, {
|
||||
frontmatter,
|
||||
components: {
|
||||
wrapper: MDXWrapper,
|
||||
a: MDXAnchor,
|
||||
}}
|
||||
/>
|
||||
</MDXModuleContext.Provider>,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,15 +49,15 @@ export const MDXAnchor = ({
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={interceptHeadingLinks}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
return React.createElement(
|
||||
"a",
|
||||
{
|
||||
href,
|
||||
onClick: interceptHeadingLinks,
|
||||
rel: "noopener noreferrer",
|
||||
target: "_blank",
|
||||
...props,
|
||||
},
|
||||
children,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,8 +13,8 @@ export const MDXWrapper = ({ children, frontmatter }: MDXWrapperProps) => {
|
||||
const nextChildren = React.Children.toArray(children);
|
||||
|
||||
if (title) {
|
||||
nextChildren.unshift(<h1 key="header-title">{title}</h1>);
|
||||
nextChildren.unshift(React.createElement("h1", { key: "header-title" }, title));
|
||||
}
|
||||
|
||||
return <div className="pf-c-content">{nextChildren}</div>;
|
||||
return React.createElement("div", { className: "pf-c-content" }, nextChildren);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { html } from "lit";
|
||||
const ACTIONS: QuickAction[] = [
|
||||
["Create a new application", "/core/applications"],
|
||||
["Check the logs", "/events/log"],
|
||||
["Explore integrations", "https://integrations.goauthentik.io/", true],
|
||||
["Explore integrations", "https://goauthentik.io/integrations/", true],
|
||||
["Manage users", "/identity/users"],
|
||||
["Check the release notes", "https://goauthentik.io/docs/releases/", true],
|
||||
];
|
||||
|
||||
@@ -11,7 +11,7 @@ import { html } from "lit";
|
||||
const ACTIONS: QuickAction[] = [
|
||||
["Create a new application", "/core/applications"],
|
||||
["Check the logs", "/events/log"],
|
||||
["Explore integrations", "https://integrations.goauthentik.io/", true],
|
||||
["Explore integrations", "https://goauthentik.io/integrations/", true],
|
||||
["Manage users", "/identity/users"],
|
||||
["Check the release notes", "https://goauthentik.io/docs/releases/", true],
|
||||
];
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
import { AKElement } from "#elements/Base";
|
||||
|
||||
import { Jsonifiable } from "type-fest";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { LitElement } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { createRef, Ref } from "lit/directives/ref.js";
|
||||
|
||||
/**
|
||||
* A subset of form associated {@linkcode ElementInternals} properties.
|
||||
*
|
||||
* @see {@linkcode FormAssociatedElement} for usage.
|
||||
*/
|
||||
export interface FormAssociated
|
||||
extends Pick<
|
||||
ElementInternals,
|
||||
| "form"
|
||||
| "validity"
|
||||
| "validationMessage"
|
||||
| "willValidate"
|
||||
| "labels"
|
||||
| "checkValidity"
|
||||
| "reportValidity"
|
||||
> {
|
||||
/**
|
||||
* The name of the input, provided to the form.
|
||||
*/
|
||||
readonly name: string | null;
|
||||
|
||||
/**
|
||||
* The type of the input, provided to the form.
|
||||
*/
|
||||
readonly type: string;
|
||||
|
||||
/**
|
||||
* Whether or not the input is required.
|
||||
*/
|
||||
required?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the input is read-only.
|
||||
*/
|
||||
readonly?: boolean;
|
||||
|
||||
/**
|
||||
* A JSON representation of the value.
|
||||
*/
|
||||
toJSON(): Jsonifiable;
|
||||
}
|
||||
|
||||
export type FormValue = File | string | FormData | null;
|
||||
|
||||
/**
|
||||
* A base element which provides reactive properties and methods for interacting with a parent form.
|
||||
*
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals | MDN}
|
||||
*/
|
||||
export abstract class FormAssociatedElement<
|
||||
V extends FormValue = string,
|
||||
T extends Jsonifiable = V extends string ? V : Jsonifiable,
|
||||
S extends FormValue = V,
|
||||
>
|
||||
extends AKElement
|
||||
implements FormAssociated
|
||||
{
|
||||
static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
|
||||
|
||||
public static readonly formAssociated = true;
|
||||
|
||||
/**
|
||||
* The internals of the element.
|
||||
*
|
||||
* @protected
|
||||
* @see {@linkcode FormAssociated}
|
||||
*/
|
||||
protected internals = this.attachInternals();
|
||||
|
||||
//#region Reactive Properties
|
||||
|
||||
@property({ type: Boolean })
|
||||
public get required() {
|
||||
return this.internals.ariaRequired === "true";
|
||||
}
|
||||
|
||||
public set required(value: boolean) {
|
||||
this.internals.ariaRequired = value ? "true" : "false";
|
||||
}
|
||||
|
||||
@property({ type: Boolean, attribute: "readonly" })
|
||||
public get readOnly() {
|
||||
return this.internals.ariaReadOnly === "true";
|
||||
}
|
||||
|
||||
public set readOnly(value: boolean) {
|
||||
this.internals.ariaReadOnly = value ? "true" : "false";
|
||||
}
|
||||
|
||||
@property({ type: Boolean })
|
||||
public get disabled() {
|
||||
return this.internals.ariaDisabled === "true";
|
||||
}
|
||||
|
||||
public set disabled(value: boolean) {
|
||||
this.internals.ariaDisabled = value ? "true" : "false";
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Aliased Properties
|
||||
|
||||
public get form(): HTMLFormElement | null {
|
||||
return this.internals.form;
|
||||
}
|
||||
|
||||
public get name() {
|
||||
return this.getAttribute("name");
|
||||
}
|
||||
|
||||
public get type() {
|
||||
return this.localName;
|
||||
}
|
||||
|
||||
public get validity() {
|
||||
return this.internals.validity;
|
||||
}
|
||||
|
||||
public get validationMessage() {
|
||||
return this.internals.validationMessage;
|
||||
}
|
||||
|
||||
public get willValidate() {
|
||||
return this.internals.willValidate;
|
||||
}
|
||||
|
||||
public get labels() {
|
||||
return this.internals.labels;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Values
|
||||
|
||||
/**
|
||||
* A reference to an element that is focusable when validation fails.
|
||||
*/
|
||||
protected anchorRef: Ref<HTMLElement>;
|
||||
|
||||
/**
|
||||
* The element that is focusable when validation fails.
|
||||
*/
|
||||
declare protected anchor: HTMLElement | null;
|
||||
|
||||
/**
|
||||
* Set the value of the form.
|
||||
*
|
||||
* @param value The value visible to the form during submission.
|
||||
* @param state The value as provided by the user.
|
||||
*/
|
||||
protected setFormValue(value: V, state?: S) {
|
||||
this.internals.setFormValue(value, state);
|
||||
|
||||
if (this.required) {
|
||||
if (value) {
|
||||
this.internals.setValidity({});
|
||||
} else {
|
||||
this.internals.setValidity(
|
||||
{
|
||||
valueMissing: true,
|
||||
},
|
||||
msg("This field is required."),
|
||||
this.anchorRef.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract toJSON(): T;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Validation
|
||||
|
||||
public checkValidity = this.internals.checkValidity.bind(this.internals);
|
||||
public reportValidity = this.internals.reportValidity.bind(this.internals);
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Set the validity state of the form.
|
||||
*
|
||||
* @param flags The validity state flags.
|
||||
* @param message The validation message.
|
||||
* @param element The element to set the validity state on.
|
||||
*/
|
||||
protected setValidity(flags: ValidityStateFlags = {}, message?: string, element?: HTMLElement) {
|
||||
this.internals.setValidity(flags, message, element ?? this.anchorRef.value);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
this.anchorRef = createRef<HTMLElement>();
|
||||
|
||||
// We define the getter here to allow the base type to be extended,
|
||||
// letting the subclasses define a more accurate HTMLElement type.
|
||||
Object.defineProperty(this, "anchor", {
|
||||
get() {
|
||||
return this.anchorRef.value || null;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
41
web/src/elements/forms/types.d.ts
vendored
41
web/src/elements/forms/types.d.ts
vendored
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @file Type definitions for form-associated elements.
|
||||
*
|
||||
* While these types are part of the HTML standard, they're not yet defined
|
||||
* in the TypeScript standard library, so we define them here.
|
||||
*
|
||||
* @expires 2026-01-01
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callbacks for form-associated elements.
|
||||
*/
|
||||
interface HTMLElement {
|
||||
/**
|
||||
* A callback invoked when the browser autofilling sets a value.
|
||||
*/
|
||||
formStateRestoreCallback?(state: FormValue, mode: "autocomplete"): void;
|
||||
/**
|
||||
* A callback invoked when the browser restores a value from a previous session.
|
||||
*/
|
||||
formStateRestoreCallback?(state: FormValue, mode: "restore"): void;
|
||||
/**
|
||||
* A callback invoked when the browser restores a value from a previous session.
|
||||
*/
|
||||
formStateRestoreCallback?(state: FormValue, mode: "restore" | "autocomplete"): void;
|
||||
|
||||
/**
|
||||
* A callback that is invoked when the form is reset.
|
||||
*/
|
||||
formResetCallback?(): void;
|
||||
|
||||
/**
|
||||
* A callback that is invoked when the element's disabled state changes.
|
||||
*/
|
||||
formDisabledCallback?(disabled: boolean): void;
|
||||
|
||||
/**
|
||||
* A callback that is invoked when the element is associated with a form.
|
||||
*/
|
||||
formAssociatedCallback?(form: HTMLFormElement): void;
|
||||
}
|
||||
@@ -124,8 +124,8 @@ export abstract class Table<T extends object>
|
||||
@property({ type: String })
|
||||
public order?: string;
|
||||
|
||||
@property({ type: String, attribute: false })
|
||||
public search?: string;
|
||||
@property({ type: String })
|
||||
public search: string = "";
|
||||
|
||||
@property({ type: Boolean })
|
||||
public checkbox = false;
|
||||
@@ -547,11 +547,11 @@ export abstract class Table<T extends object>
|
||||
return html`<div class="pf-c-toolbar__group pf-m-search-filter ${isQL ? "ql" : ""}">
|
||||
<ak-table-search
|
||||
class="pf-c-toolbar__item pf-m-search-filter ${isQL ? "ql" : ""}"
|
||||
.defaultValue=${this.search}
|
||||
value=${ifDefined(this.search)}
|
||||
label=${ifDefined(this.searchLabel)}
|
||||
placeholder=${ifDefined(this.searchPlaceholder)}
|
||||
.onSearch=${this.#searchListener}
|
||||
.supportsQL=${this.supportsQL}
|
||||
?supportsQL=${this.supportsQL}
|
||||
.apiResponse=${this.data}
|
||||
>
|
||||
</ak-table-search>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { msg } from "@lit/localize";
|
||||
import { css, CSSResult, html, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
@@ -17,23 +16,17 @@ import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
@customElement("ak-table-search")
|
||||
export class TableSearchForm extends WithLicenseSummary(AKElement) {
|
||||
@property({ type: String, reflect: false })
|
||||
public defaultValue?: string;
|
||||
export class TableSearch extends WithLicenseSummary(AKElement) {
|
||||
@property()
|
||||
public value?: string;
|
||||
|
||||
@property({ type: String })
|
||||
public label = msg("Table Search");
|
||||
|
||||
@property({ type: String })
|
||||
public placeholder = msg("Search...");
|
||||
|
||||
@property({ attribute: false })
|
||||
@property({ type: Boolean })
|
||||
public supportsQL: boolean = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public apiResponse?: PaginatedResponse<unknown>;
|
||||
|
||||
@property({ attribute: false })
|
||||
@property()
|
||||
public onSearch?: (value: string) => void;
|
||||
|
||||
static styles: CSSResult[] = [
|
||||
@@ -52,26 +45,25 @@ export class TableSearchForm extends WithLicenseSummary(AKElement) {
|
||||
`,
|
||||
];
|
||||
|
||||
#formRef = createRef<HTMLFormElement>();
|
||||
|
||||
public reset = (): void => {
|
||||
this.#formRef.value?.reset();
|
||||
|
||||
this.onSearch?.("");
|
||||
public reset = () => {
|
||||
if (!this.onSearch) return;
|
||||
this.value = "";
|
||||
this.onSearch("");
|
||||
};
|
||||
|
||||
#submitListener = (event: SubmitEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
const form = this.#formRef.value;
|
||||
|
||||
if (!form || !this.onSearch) return;
|
||||
|
||||
form.reportValidity();
|
||||
if (!this.onSearch) return;
|
||||
|
||||
const form = event.target as HTMLFormElement;
|
||||
const data = new FormData(form);
|
||||
|
||||
const value = data.get("search")?.toString() ?? "";
|
||||
const value = data.get("search")?.toString().trim();
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onSearch(value);
|
||||
};
|
||||
@@ -79,31 +71,27 @@ export class TableSearchForm extends WithLicenseSummary(AKElement) {
|
||||
renderInput(): TemplateResult {
|
||||
if (this.supportsQL && this.hasEnterpriseLicense) {
|
||||
return html`<ak-search-ql
|
||||
aria-label=${ifDefined(this.label)}
|
||||
name="search"
|
||||
required
|
||||
placeholder=${ifDefined(this.placeholder)}
|
||||
value=${ifDefined(this.defaultValue)}
|
||||
.apiResponse=${this.apiResponse}
|
||||
.value=${this.value}
|
||||
.onSearch=${(value: string) => {
|
||||
if (!this.onSearch) return;
|
||||
this.onSearch(value);
|
||||
}}
|
||||
name="search"
|
||||
></ak-search-ql>`;
|
||||
}
|
||||
|
||||
return html`<input
|
||||
aria-label=${ifDefined(this.label)}
|
||||
name="search"
|
||||
required
|
||||
placeholder=${ifDefined(this.placeholder)}
|
||||
value=${ifDefined(this.defaultValue)}
|
||||
class="pf-c-form-control"
|
||||
name="search"
|
||||
type="search"
|
||||
placeholder=${msg("Search...")}
|
||||
value="${ifDefined(this.value)}"
|
||||
/>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form
|
||||
${ref(this.#formRef)}
|
||||
class="pf-c-input-group"
|
||||
@submit=${this.#submitListener}
|
||||
>
|
||||
return html`<form class="pf-c-input-group" method="get" @submit=${this.#submitListener}>
|
||||
${this.renderInput()}
|
||||
<button
|
||||
aria-label=${msg("Clear search")}
|
||||
@@ -122,6 +110,6 @@ export class TableSearchForm extends WithLicenseSummary(AKElement) {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-table-search": TableSearchForm;
|
||||
"ak-table-search": TableSearch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,19 +54,6 @@ export type LitPropertyRecord<T extends object> = {
|
||||
*/
|
||||
export type LitPropertyKey<K> = K extends string ? `.${K}` | `?${K}` | K : K;
|
||||
|
||||
/**
|
||||
* A React-like functional component. Used to render a component in a template.
|
||||
*
|
||||
* @template P The type of the props object.
|
||||
* @param props The props object.
|
||||
* @param children The children to render.
|
||||
* @returns The rendered template.
|
||||
*/
|
||||
export type LitFC<P> = (
|
||||
props: P,
|
||||
children?: SlottedTemplateResult,
|
||||
) => SlottedTemplateResult | SlottedTemplateResult[];
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Host/Controller
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AKElement } from "#elements/Base";
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, CSSResult, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
|
||||
|
||||
@@ -17,44 +18,24 @@ export class FormStatic extends AKElement {
|
||||
static styles: CSSResult[] = [
|
||||
PFAvatar,
|
||||
css`
|
||||
/* Form with user */
|
||||
.form-control-static {
|
||||
margin-block-start: var(--pf-global--spacer--sm);
|
||||
margin-top: var(--pf-global--spacer--sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--pf-global--spacer--sm);
|
||||
|
||||
.pf-c-avatar {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.primary-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1 1 auto;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.username {
|
||||
flex: 1 1 auto;
|
||||
text-align: left;
|
||||
max-width: 20rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow-wrap: break-word;
|
||||
|
||||
display: box;
|
||||
display: -webkit-box;
|
||||
line-clamp: 3;
|
||||
-webkit-line-clamp: 3;
|
||||
box-orient: vertical;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.links {
|
||||
flex: 0 0 auto;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
.form-control-static .avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.form-control-static img {
|
||||
margin-right: var(--pf-global--spacer--xs);
|
||||
}
|
||||
.form-control-static a {
|
||||
padding-top: var(--pf-global--spacer--xs);
|
||||
padding-bottom: var(--pf-global--spacer--xs);
|
||||
line-height: var(--pf-global--spacer--xl);
|
||||
}
|
||||
`,
|
||||
];
|
||||
@@ -63,22 +44,17 @@ export class FormStatic extends AKElement {
|
||||
if (!this.user) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="form-control-static">
|
||||
<div class="primary-content">
|
||||
${this.userAvatar
|
||||
? html`<img
|
||||
class="pf-c-avatar"
|
||||
src=${this.userAvatar}
|
||||
alt=${msg("User's avatar")}
|
||||
/>`
|
||||
: nothing}
|
||||
<div class="username" aria-label=${msg("Username")}>${this.user}</div>
|
||||
</div>
|
||||
<div class="links">
|
||||
<slot name="link"></slot>
|
||||
<div class="avatar">
|
||||
<img
|
||||
class="pf-c-avatar"
|
||||
src="${ifDefined(this.userAvatar)}"
|
||||
alt="${msg("User's avatar")}"
|
||||
/>
|
||||
${this.user}
|
||||
</div>
|
||||
<slot name="link"></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -33,10 +33,13 @@ export class FlowCard extends AKElement {
|
||||
PFLogin,
|
||||
PFTitle,
|
||||
css`
|
||||
.pf-c-login__main-footer {
|
||||
display: block;
|
||||
slot[name="footer"],
|
||||
slot[name="footer-band"] {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
slot[name="footer-band"] {
|
||||
text-align: center;
|
||||
background-color: var(--pf-c-login__main-footer-band--BackgroundColor);
|
||||
|
||||
@@ -24,7 +24,6 @@ export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
|
||||
css`
|
||||
.icon-description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icon-description i {
|
||||
font-size: 2em;
|
||||
|
||||
@@ -34,7 +34,7 @@ export class BaseDeviceStage<
|
||||
css`
|
||||
.pf-c-form__group.pf-m-action {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
gap: 16px;
|
||||
margin-top: 0;
|
||||
margin-bottom: calc(var(--pf-c-form__group--m-action--MarginTop) / 2);
|
||||
flex-direction: column;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ListenerController } from "#elements/utils/listenerController";
|
||||
import { randomId } from "#elements/utils/randomId";
|
||||
|
||||
import { BaseStage } from "#flow/stages/base";
|
||||
import { CaptchaHandler, CaptchaProvider, iframeTemplate } from "#flow/stages/captcha/shared";
|
||||
import { CaptchaHandler, iframeTemplate } from "#flow/stages/captcha/shared";
|
||||
|
||||
import { CaptchaChallenge, CaptchaChallengeResponseRequest } from "@goauthentik/api";
|
||||
|
||||
@@ -109,7 +109,7 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
//#region State
|
||||
|
||||
@state()
|
||||
protected activeHandler: CaptchaProvider | null = null;
|
||||
protected activeHandler: CaptchaHandler | null = null;
|
||||
|
||||
@state()
|
||||
protected error: string | null = null;
|
||||
@@ -265,12 +265,7 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Mapping of captcha provider names to their respective JS API global.
|
||||
*
|
||||
* Note that this is a `Map` to ensure the preferred order of discovering provider globals.
|
||||
*/
|
||||
#handlers = new Map<CaptchaProvider, CaptchaHandler>([
|
||||
#handlers = new Map<string, CaptchaHandler>([
|
||||
[
|
||||
"grecaptcha",
|
||||
{
|
||||
@@ -420,7 +415,7 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Resizing
|
||||
//#region Listeners
|
||||
|
||||
#loadListener = () => {
|
||||
const iframe = this.#iframeRef.value;
|
||||
@@ -428,73 +423,17 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
|
||||
if (!iframe || !contentDocument) return;
|
||||
|
||||
let synchronizeHeight: () => void;
|
||||
const resizeListener: ResizeObserverCallback = () => {
|
||||
if (!this.#iframeRef) return;
|
||||
|
||||
if (this.activeHandler === CaptchaProvider.reCAPTCHA) {
|
||||
// reCAPTCHA's use of nested iframes prevents their internal resize observer from
|
||||
// reporting the correct height back to our iframe, so we have to do it ourselves.
|
||||
const target = contentDocument.getElementById("ak-container");
|
||||
|
||||
synchronizeHeight = () => {
|
||||
if (!this.#iframeRef) return;
|
||||
if (!target) return;
|
||||
|
||||
const target = contentDocument.getElementById("ak-container");
|
||||
this.iframeHeight = Math.round(target.clientHeight);
|
||||
};
|
||||
|
||||
if (!target) return;
|
||||
|
||||
const innerIFrame = contentDocument.querySelector<HTMLIFrameElement>(
|
||||
'iframe[style~="height:"]',
|
||||
);
|
||||
|
||||
const innerBottom = innerIFrame?.getBoundingClientRect().bottom ?? 0;
|
||||
|
||||
const actualHeight = Math.max(innerBottom, target.clientHeight);
|
||||
|
||||
this.iframeHeight = Math.round(actualHeight * 1.1);
|
||||
|
||||
if (innerIFrame?.parentElement) {
|
||||
innerIFrame.parentElement.style.height = `${actualHeight}px`;
|
||||
}
|
||||
};
|
||||
|
||||
// We watch for any newly inserted iframes, as they may alter the height
|
||||
// of the parent iframe...
|
||||
const mutationObserver = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type !== "childList") continue;
|
||||
|
||||
for (const node of mutation.addedNodes as NodeListOf<HTMLElement>) {
|
||||
if (node.tagName !== "IFRAME") continue;
|
||||
|
||||
// And then resize the iframe to match the new size.
|
||||
//
|
||||
// This doesn't fix the issue entirely since the challenge frame
|
||||
// doesn't yet know the correct height, but at least the user can
|
||||
// try to load the challenge again with the correct height.
|
||||
|
||||
resizeObserver.observe(node as HTMLIFrameElement);
|
||||
|
||||
requestAnimationFrame(synchronizeHeight);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mutationObserver.observe(contentDocument.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
} else {
|
||||
synchronizeHeight = () => {
|
||||
if (!this.#iframeRef) return;
|
||||
|
||||
const target = contentDocument.getElementById("ak-container");
|
||||
|
||||
if (!target) return;
|
||||
|
||||
this.iframeHeight = Math.round(target.clientHeight);
|
||||
};
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver(synchronizeHeight);
|
||||
const resizeObserver = new ResizeObserver(resizeListener);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
resizeObserver.observe(contentDocument.body);
|
||||
@@ -503,26 +442,22 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
});
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Loading
|
||||
|
||||
#scriptLoadListener = async (): Promise<void> => {
|
||||
console.debug("authentik/stages/captcha: script loaded");
|
||||
|
||||
this.error = null;
|
||||
this.#iframeLoaded = false;
|
||||
|
||||
for (const name of this.#handlers.keys()) {
|
||||
for (const [name, handler] of this.#handlers) {
|
||||
if (!Object.hasOwn(window, name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.#run(name);
|
||||
await this.#run(handler);
|
||||
console.debug(`authentik/stages/captcha[${name}]: handler succeeded`);
|
||||
|
||||
this.activeHandler = name;
|
||||
this.activeHandler = handler;
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
@@ -534,9 +469,7 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
}
|
||||
};
|
||||
|
||||
async #run(captchaProvider: CaptchaProvider) {
|
||||
const handler = this.#handlers.get(captchaProvider)!;
|
||||
|
||||
async #run(handler: CaptchaHandler) {
|
||||
if (this.challenge.interactive) {
|
||||
const iframe = this.#iframeRef.value;
|
||||
|
||||
@@ -545,44 +478,18 @@ export class CaptchaStage extends BaseStage<CaptchaChallenge, CaptchaChallengeRe
|
||||
return;
|
||||
}
|
||||
|
||||
const { contentDocument } = iframe;
|
||||
|
||||
if (!contentDocument) {
|
||||
console.debug(
|
||||
`authentik/stages/captcha: No iframe content window found, skipping.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug(`authentik/stages/captcha: Rendering interactive.`);
|
||||
|
||||
const captchaElement = handler.interactive();
|
||||
const template = iframeTemplate(captchaElement, {
|
||||
challengeURL: this.challenge.jsUrl,
|
||||
theme: this.activeTheme,
|
||||
});
|
||||
const template = iframeTemplate(captchaElement, this.challenge.jsUrl);
|
||||
|
||||
if (captchaProvider === CaptchaProvider.reCAPTCHA) {
|
||||
// reCAPTCHA's domain verification can't seem to penetrate the true origin
|
||||
// of the page when loaded from a blob URL, likely due to their double-nested
|
||||
// iframe structure.
|
||||
// We fallback to the deprecated `document.write` to get around this.
|
||||
this.#iframeSource = "about:blank";
|
||||
contentDocument.open();
|
||||
contentDocument.write(template);
|
||||
contentDocument.close();
|
||||
URL.revokeObjectURL(this.#iframeSource);
|
||||
|
||||
// this.#loadListener();
|
||||
} else {
|
||||
URL.revokeObjectURL(this.#iframeSource);
|
||||
const url = URL.createObjectURL(new Blob([template], { type: "text/html" }));
|
||||
|
||||
const url = URL.createObjectURL(new Blob([template], { type: "text/html" }));
|
||||
this.#iframeSource = url;
|
||||
|
||||
this.#iframeSource = url;
|
||||
|
||||
iframe.src = url;
|
||||
}
|
||||
iframe.src = url;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
import type { ResolvedUITheme } from "#common/theme";
|
||||
|
||||
import { createDocumentTemplate } from "#elements/utils/iframe";
|
||||
|
||||
import { html, TemplateResult } from "lit";
|
||||
|
||||
/**
|
||||
* Mapping of captcha provider names to their respective JS API global.
|
||||
*/
|
||||
export const CaptchaProvider = {
|
||||
reCAPTCHA: "grecaptcha",
|
||||
hCaptcha: "hcaptcha",
|
||||
Turnstile: "turnstile",
|
||||
} as const satisfies Record<string, string>;
|
||||
|
||||
export type CaptchaProvider = (typeof CaptchaProvider)[keyof typeof CaptchaProvider];
|
||||
|
||||
export interface CaptchaHandler {
|
||||
interactive(): TemplateResult;
|
||||
execute(): Promise<void>;
|
||||
@@ -22,29 +9,6 @@ export interface CaptchaHandler {
|
||||
refresh(): Promise<void>;
|
||||
}
|
||||
|
||||
const ThemeColor = {
|
||||
dark: "#18191a",
|
||||
light: "#ffffff",
|
||||
} as const satisfies Record<ResolvedUITheme, string>;
|
||||
|
||||
export function themeMeta(theme: ResolvedUITheme) {
|
||||
switch (theme) {
|
||||
case "dark":
|
||||
return html`
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<meta name="theme-color" content=${ThemeColor.dark} />
|
||||
`;
|
||||
case "light":
|
||||
return html` <meta name="color-scheme" content="light" />
|
||||
<meta name="theme-color" content=${ThemeColor.light} />`;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IFrameTemplateInit {
|
||||
challengeURL: string;
|
||||
theme: ResolvedUITheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* A container iframe for a hosted Captcha, with an event emitter to monitor
|
||||
* when the Captcha forces a resize.
|
||||
@@ -53,17 +17,10 @@ export interface IFrameTemplateInit {
|
||||
* margin, adding 2rem of height to our container adds padding and prevents scrollbars
|
||||
* or hidden rendering.
|
||||
*/
|
||||
export function iframeTemplate(
|
||||
children: TemplateResult,
|
||||
{ challengeURL, theme }: IFrameTemplateInit,
|
||||
) {
|
||||
export function iframeTemplate(children: TemplateResult, challengeURL: string): string {
|
||||
return createDocumentTemplate({
|
||||
head: html`
|
||||
<meta charset="UTF-8" />
|
||||
head: html`<meta charset="UTF-8" />
|
||||
|
||||
${themeMeta(theme)}
|
||||
`,
|
||||
body: html`
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
@@ -86,11 +43,6 @@ export function iframeTemplate(
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
background: ${ThemeColor[theme]};
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -106,9 +58,8 @@ export function iframeTemplate(
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
${children}
|
||||
<script onload="loadListener()" src="${challengeURL}"></script>
|
||||
`,
|
||||
</style>`,
|
||||
body: html`${children}
|
||||
<script onload="loadListener()" src="${challengeURL}"></script> `,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,12 +58,6 @@ export class IdentificationStage extends BaseStage<
|
||||
PFButton,
|
||||
...AkRememberMeController.styles,
|
||||
css`
|
||||
.pf-c-form__group.pf-m-action {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* login page's icons */
|
||||
.pf-c-login__main-footer-links-item button {
|
||||
background-color: transparent;
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"baseUrl": ".",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@goauthentik/lit-jsx",
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
// TODO: We should enable this when when we're ready to enforce it.
|
||||
"noUncheckedIndexedAccess": false,
|
||||
|
||||
@@ -1466,11 +1466,6 @@
|
||||
<source>No form found</source>
|
||||
<target>Kein Formular gefunden</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Das Formular hat keinen Wert zum Absenden zurückgegeben</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -10042,6 +10037,12 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1177,10 +1177,6 @@
|
||||
<source>No form found</source>
|
||||
<target>No form found</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Form didn't return a promise for submitting</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
<target>Select type</target>
|
||||
@@ -7921,6 +7917,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1466,11 +1466,6 @@
|
||||
<source>No form found</source>
|
||||
<target>No se encontró ningún formulario</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>El formulario no devolvió una promesa para enviarla</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -10090,6 +10085,12 @@ El valor de este campo se compara con el atributo de pertenencia del usuario.</t
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1466,11 +1466,6 @@
|
||||
<source>No form found</source>
|
||||
<target>Aucun formulaire trouvé</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Le formulaire n'a pas retourné de promesse de soumission</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -10113,6 +10108,12 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
<target>None</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1466,11 +1466,6 @@
|
||||
<source>No form found</source>
|
||||
<target>Nessun modulo trovato</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Il modulo non ha restituito una promise di invio</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -10046,6 +10041,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1449,11 +1449,6 @@
|
||||
<source>No form found</source>
|
||||
<target>찾을 수 없음</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>양식에서 제출하기로 한 것을 반환하지 않음</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -9368,6 +9363,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1459,11 +1459,6 @@
|
||||
<source>No form found</source>
|
||||
<target>Geen formulier gevonden</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Formulier heeft geen promise geretourneerd voor indienen</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -9274,6 +9269,12 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1466,11 +1466,6 @@
|
||||
<source>No form found</source>
|
||||
<target>Nie znaleziono formularza</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Formularz nie zwrócił obietnicy do przesłania</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -9689,6 +9684,12 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1456,11 +1456,6 @@
|
||||
<source>No form found</source>
|
||||
<target>Ńō ƒōŕḿ ƒōũńď</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Ƒōŕḿ ďĩďń'ţ ŕēţũŕń à ƥŕōḿĩśē ƒōŕ śũƀḿĩţţĩńĝ</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -9698,4 +9693,10 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body></file></xliff>
|
||||
|
||||
@@ -1466,11 +1466,6 @@
|
||||
<source>No form found</source>
|
||||
<target>Форма не найдена</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Форма не вернула обещание отправить</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -9780,6 +9775,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
@@ -1455,11 +1455,6 @@
|
||||
<source>No form found</source>
|
||||
<target>Form bulunamadı</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s45935843b1b5b496">
|
||||
<source>Form didn't return a promise for submitting</source>
|
||||
<target>Form göndermek için bir söz vermedi</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s74475586afc1fb0f">
|
||||
<source>Select type</source>
|
||||
@@ -9753,6 +9748,12 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
|
||||
</trans-unit>
|
||||
<trans-unit id="s669b18c6d2d9c95b">
|
||||
<source>None</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s2de7890b100c4f4c">
|
||||
<source>Flags</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sf0b34d32a602aee8">
|
||||
<source>Modify flags to opt into new authentik behaviours early.</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user