mirror of
https://github.com/goauthentik/authentik
synced 2026-04-26 01:25:02 +02:00
Compare commits
3 Commits
flows/conc
...
admin/vers
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbce9611d2 | ||
|
|
e6643a69cd | ||
|
|
0fdeaee559 |
@@ -42,7 +42,11 @@ class Exporter:
|
|||||||
if model in self.excluded_models:
|
if model in self.excluded_models:
|
||||||
continue
|
continue
|
||||||
for obj in self.get_model_instances(model):
|
for obj in self.get_model_instances(model):
|
||||||
yield BlueprintEntry.from_model(obj)
|
yield BlueprintEntry.from_model(self.alter_model(obj))
|
||||||
|
|
||||||
|
def alter_model(self, model: Model):
|
||||||
|
"""Hook to modify the model before exporting"""
|
||||||
|
return model
|
||||||
|
|
||||||
def get_model_instances(self, model: type[Model]) -> QuerySet:
|
def get_model_instances(self, model: type[Model]) -> QuerySet:
|
||||||
"""Return a queryset for `model`. Can be used to filter some
|
"""Return a queryset for `model`. Can be used to filter some
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
@@ -10,17 +11,21 @@ from rest_framework.decorators import action
|
|||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.fields import CharField, IntegerField
|
from rest_framework.fields import CharField, IntegerField
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.renderers import BaseRenderer
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.validators import UniqueValidator
|
from rest_framework.validators import UniqueValidator
|
||||||
|
from rest_framework.views import APIView
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||||
from authentik.core.models import User, UserTypes
|
from authentik.core.models import User, UserTypes
|
||||||
|
from authentik.enterprise.bundle import generate_support_bundle
|
||||||
from authentik.enterprise.license import LicenseKey, LicenseSummarySerializer
|
from authentik.enterprise.license import LicenseKey, LicenseSummarySerializer
|
||||||
from authentik.enterprise.models import License
|
from authentik.enterprise.models import License
|
||||||
from authentik.rbac.decorators import permission_required
|
from authentik.rbac.decorators import permission_required
|
||||||
|
from authentik.rbac.permissions import HasPermission
|
||||||
from authentik.tenants.utils import get_unique_identifier
|
from authentik.tenants.utils import get_unique_identifier
|
||||||
|
|
||||||
|
|
||||||
@@ -147,3 +152,24 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
|
|||||||
)
|
)
|
||||||
response.is_valid(raise_exception=True)
|
response.is_valid(raise_exception=True)
|
||||||
return Response(response.data)
|
return Response(response.data)
|
||||||
|
|
||||||
|
|
||||||
|
class BinaryRenderer(BaseRenderer):
|
||||||
|
media_type = "application/gzip"
|
||||||
|
format = "bin"
|
||||||
|
|
||||||
|
|
||||||
|
class SupportBundleView(APIView):
|
||||||
|
"""Generate a support bundle."""
|
||||||
|
|
||||||
|
permission_classes = [HasPermission("authentik_rbac.view_system_info")]
|
||||||
|
pagination_class = None
|
||||||
|
filter_backends = []
|
||||||
|
renderer_classes = [BinaryRenderer]
|
||||||
|
|
||||||
|
@extend_schema(responses=bytes, request=None)
|
||||||
|
def post(self, request: Request) -> Response:
|
||||||
|
"""Generate a support bundle."""
|
||||||
|
response = HttpResponse(generate_support_bundle(), content_type=BinaryRenderer.media_type)
|
||||||
|
response["Content-Disposition"] = 'attachment; filename="authentik_support.tgz"'
|
||||||
|
return response
|
||||||
|
|||||||
53
authentik/enterprise/bundle.py
Normal file
53
authentik/enterprise/bundle.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import re
|
||||||
|
from io import BytesIO
|
||||||
|
from tarfile import TarInfo, open
|
||||||
|
|
||||||
|
from django.db.models import Model
|
||||||
|
from django.db.models.fields import CharField, SlugField, TextField
|
||||||
|
from django.db.models.fields.json import JSONField
|
||||||
|
|
||||||
|
from authentik.blueprints.v1.exporter import Exporter
|
||||||
|
from authentik.core.models import User
|
||||||
|
from lifecycle.support import encrypt, generate
|
||||||
|
|
||||||
|
SENSITIVE_VALUE_PLACEHOLDER = "<REDACTED>"
|
||||||
|
|
||||||
|
|
||||||
|
class SupportExporter(Exporter):
|
||||||
|
"""Blueprint exporter which censors sensitive model attributes"""
|
||||||
|
|
||||||
|
sensitive_fields = re.compile(
|
||||||
|
# Partially taken from Django's SafeExceptionReporterFilter
|
||||||
|
"API|AUTH|TOKEN|KEY|SECRET|PASS|SIGNATURE|CREDENTIALS",
|
||||||
|
re.I,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.excluded_models.append(User)
|
||||||
|
|
||||||
|
def alter_model(self, model: Model):
|
||||||
|
for field in model._meta.fields:
|
||||||
|
if not self.sensitive_fields.search(field.name):
|
||||||
|
continue
|
||||||
|
if isinstance(field, TextField | CharField | SlugField):
|
||||||
|
setattr(model, field.name, SENSITIVE_VALUE_PLACEHOLDER)
|
||||||
|
elif isinstance(field, JSONField):
|
||||||
|
setattr(model, field.name, {})
|
||||||
|
return model
|
||||||
|
|
||||||
|
|
||||||
|
def generate_support_bundle():
|
||||||
|
fh = BytesIO()
|
||||||
|
exporter = SupportExporter()
|
||||||
|
files = {
|
||||||
|
"authentik/support.jwe": encrypt(generate()),
|
||||||
|
"authentik/blueprint.yaml": exporter.export_to_string(),
|
||||||
|
}
|
||||||
|
with open(fileobj=fh, mode="w:gz") as tar:
|
||||||
|
for path, file in files.items():
|
||||||
|
info = TarInfo(path)
|
||||||
|
info.size = len(file)
|
||||||
|
tar.addfile(info, BytesIO(file.encode()))
|
||||||
|
final_data = fh.getvalue()
|
||||||
|
return final_data
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
"""API URLs"""
|
"""API URLs"""
|
||||||
|
|
||||||
from authentik.enterprise.api import LicenseViewSet
|
from django.urls import path
|
||||||
|
|
||||||
|
from authentik.enterprise.api import LicenseViewSet, SupportBundleView
|
||||||
|
|
||||||
api_urlpatterns = [
|
api_urlpatterns = [
|
||||||
("enterprise/license", LicenseViewSet),
|
("enterprise/license", LicenseViewSet),
|
||||||
|
path(
|
||||||
|
"enterprise/support_bundle/", SupportBundleView.as_view(), name="enterprise_support_bundle"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ elif [[ "$1" == "healthcheck" ]]; then
|
|||||||
elif [[ "$1" == "dump_config" ]]; then
|
elif [[ "$1" == "dump_config" ]]; then
|
||||||
shift
|
shift
|
||||||
exec python -m authentik.lib.config $@
|
exec python -m authentik.lib.config $@
|
||||||
|
elif [[ "$1" == "support" ]]; then
|
||||||
|
wait_for_db
|
||||||
|
exec python -m lifecycle.support
|
||||||
elif [[ "$1" == "debug" ]]; then
|
elif [[ "$1" == "debug" ]]; then
|
||||||
exec sleep infinity
|
exec sleep infinity
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ from os import environ, system
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from psycopg import Connection, Cursor, connect
|
from psycopg import Connection, Cursor
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.lib.config import CONFIG, django_db_config
|
from authentik.lib.config import CONFIG, django_db_config
|
||||||
|
from lifecycle.wait_for_db import get_postgres
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
ADV_LOCK_UID = 1000
|
ADV_LOCK_UID = 1000
|
||||||
@@ -71,17 +72,7 @@ def release_lock(cursor: Cursor):
|
|||||||
|
|
||||||
|
|
||||||
def run_migrations():
|
def run_migrations():
|
||||||
conn = connect(
|
conn = get_postgres()
|
||||||
dbname=CONFIG.get("postgresql.name"),
|
|
||||||
user=CONFIG.get("postgresql.user"),
|
|
||||||
password=CONFIG.get("postgresql.password"),
|
|
||||||
host=CONFIG.get("postgresql.host"),
|
|
||||||
port=CONFIG.get_int("postgresql.port"),
|
|
||||||
sslmode=CONFIG.get("postgresql.sslmode"),
|
|
||||||
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
|
|
||||||
sslcert=CONFIG.get("postgresql.sslcert"),
|
|
||||||
sslkey=CONFIG.get("postgresql.sslkey"),
|
|
||||||
)
|
|
||||||
curr = conn.cursor()
|
curr = conn.cursor()
|
||||||
try:
|
try:
|
||||||
wait_for_lock(curr)
|
wait_for_lock(curr)
|
||||||
|
|||||||
111
lifecycle/support.py
Normal file
111
lifecycle/support.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import platform
|
||||||
|
from hashlib import sha512
|
||||||
|
from pprint import pprint
|
||||||
|
from ssl import OPENSSL_VERSION
|
||||||
|
from sys import version as python_version
|
||||||
|
|
||||||
|
from cryptography.exceptions import InternalError
|
||||||
|
from cryptography.hazmat.backends.openssl.backend import backend
|
||||||
|
from jwcrypto.common import json_encode
|
||||||
|
from jwcrypto.jwe import JWE
|
||||||
|
from jwcrypto.jwk import JWK
|
||||||
|
from jwt import encode
|
||||||
|
from psutil import cpu_count, virtual_memory
|
||||||
|
from redis import Redis
|
||||||
|
|
||||||
|
from authentik import get_full_version
|
||||||
|
from authentik.lib.config import CONFIG
|
||||||
|
from authentik.lib.utils.reflection import get_env
|
||||||
|
from authentik.root.install_id import get_install_id_raw
|
||||||
|
from lifecycle.wait_for_db import get_postgres, get_redis
|
||||||
|
|
||||||
|
try:
|
||||||
|
backend._enable_fips()
|
||||||
|
except InternalError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_version_history():
|
||||||
|
with get_postgres() as postgres:
|
||||||
|
cur = postgres.cursor()
|
||||||
|
cur.execute("""SELECT "timestamp", "version", "build" FROM authentik_version_history;""")
|
||||||
|
for x, y, z in cur.fetchall():
|
||||||
|
yield (x.timestamp(), y, z)
|
||||||
|
|
||||||
|
|
||||||
|
def get_postgres_version():
|
||||||
|
with get_postgres() as postgres:
|
||||||
|
cur = postgres.cursor()
|
||||||
|
cur.execute("""SELECT version();""")
|
||||||
|
return cur.fetchone()[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_redis_version():
|
||||||
|
redis: Redis = get_redis()
|
||||||
|
version = redis.info()
|
||||||
|
redis.close()
|
||||||
|
return f"{version["redis_version"]} {version["redis_mode"]} {version["os"]}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_limited_config():
|
||||||
|
return {
|
||||||
|
"postgresql": {
|
||||||
|
"host": CONFIG.get("postgresql.host"),
|
||||||
|
},
|
||||||
|
"redis": {
|
||||||
|
"host": CONFIG.get("redis.host"),
|
||||||
|
},
|
||||||
|
"debug": CONFIG.get_bool("debug"),
|
||||||
|
"log_level": CONFIG.get("log_level"),
|
||||||
|
"error_reporting": {
|
||||||
|
"enabled": CONFIG.get_bool("error_reporting.enabled"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate():
|
||||||
|
payload = {
|
||||||
|
"version": {
|
||||||
|
"history": list(get_version_history()),
|
||||||
|
"current": get_full_version(),
|
||||||
|
"postgres": get_postgres_version(),
|
||||||
|
"redis": get_redis_version(),
|
||||||
|
"ssl": OPENSSL_VERSION,
|
||||||
|
"python": python_version,
|
||||||
|
},
|
||||||
|
"env": get_env(),
|
||||||
|
"install_id_hash": sha512(get_install_id_raw().encode("ascii")).hexdigest()[:16],
|
||||||
|
"system": {
|
||||||
|
"cpu": {"count": cpu_count()},
|
||||||
|
"fips": backend._fips_enabled,
|
||||||
|
"memory_bytes": virtual_memory().total,
|
||||||
|
"architecture": platform.machine(),
|
||||||
|
"platform": platform.platform(),
|
||||||
|
"uname": " ".join(platform.uname()),
|
||||||
|
},
|
||||||
|
"config": get_limited_config(),
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt(raw):
|
||||||
|
with open("authentik/enterprise/public.pem", "rb") as _key:
|
||||||
|
key = JWK.from_pem(_key.read())
|
||||||
|
jwe = JWE(
|
||||||
|
encode(raw, "foo"),
|
||||||
|
json_encode(
|
||||||
|
{
|
||||||
|
"alg": "ECDH-ES+A256KW",
|
||||||
|
"enc": "A256CBC-HS512",
|
||||||
|
"typ": "JWE",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
jwe.add_recipient(key)
|
||||||
|
return jwe.serialize(compact=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
data = generate()
|
||||||
|
snippet = encrypt(data)
|
||||||
|
pprint(data)
|
||||||
@@ -13,23 +13,32 @@ from authentik.lib.config import CONFIG, redis_url
|
|||||||
CHECK_THRESHOLD = 30
|
CHECK_THRESHOLD = 30
|
||||||
|
|
||||||
|
|
||||||
|
def get_postgres():
|
||||||
|
return connect(
|
||||||
|
dbname=CONFIG.get("postgresql.name"),
|
||||||
|
user=CONFIG.get("postgresql.user"),
|
||||||
|
password=CONFIG.get("postgresql.password"),
|
||||||
|
host=CONFIG.get("postgresql.host"),
|
||||||
|
port=CONFIG.get_int("postgresql.port"),
|
||||||
|
sslmode=CONFIG.get("postgresql.sslmode"),
|
||||||
|
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
|
||||||
|
sslcert=CONFIG.get("postgresql.sslcert"),
|
||||||
|
sslkey=CONFIG.get("postgresql.sslkey"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_redis():
|
||||||
|
url = CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db"))
|
||||||
|
return Redis.from_url(url)
|
||||||
|
|
||||||
|
|
||||||
def check_postgres():
|
def check_postgres():
|
||||||
attempt = 0
|
attempt = 0
|
||||||
while True:
|
while True:
|
||||||
if attempt >= CHECK_THRESHOLD:
|
if attempt >= CHECK_THRESHOLD:
|
||||||
sysexit(1)
|
sysexit(1)
|
||||||
try:
|
try:
|
||||||
conn = connect(
|
conn = get_postgres()
|
||||||
dbname=CONFIG.refresh("postgresql.name"),
|
|
||||||
user=CONFIG.refresh("postgresql.user"),
|
|
||||||
password=CONFIG.refresh("postgresql.password"),
|
|
||||||
host=CONFIG.refresh("postgresql.host"),
|
|
||||||
port=CONFIG.get_int("postgresql.port"),
|
|
||||||
sslmode=CONFIG.get("postgresql.sslmode"),
|
|
||||||
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
|
|
||||||
sslcert=CONFIG.get("postgresql.sslcert"),
|
|
||||||
sslkey=CONFIG.get("postgresql.sslkey"),
|
|
||||||
)
|
|
||||||
conn.cursor()
|
conn.cursor()
|
||||||
break
|
break
|
||||||
except OperationalError as exc:
|
except OperationalError as exc:
|
||||||
@@ -41,13 +50,12 @@ def check_postgres():
|
|||||||
|
|
||||||
|
|
||||||
def check_redis():
|
def check_redis():
|
||||||
url = CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db"))
|
|
||||||
attempt = 0
|
attempt = 0
|
||||||
while True:
|
while True:
|
||||||
if attempt >= CHECK_THRESHOLD:
|
if attempt >= CHECK_THRESHOLD:
|
||||||
sysexit(1)
|
sysexit(1)
|
||||||
try:
|
try:
|
||||||
redis = Redis.from_url(url)
|
redis = get_redis()
|
||||||
redis.ping()
|
redis.ping()
|
||||||
break
|
break
|
||||||
except RedisError as exc:
|
except RedisError as exc:
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ dependencies = [
|
|||||||
"opencontainers==0.0.15",
|
"opencontainers==0.0.15",
|
||||||
"packaging==25.0",
|
"packaging==25.0",
|
||||||
"paramiko==3.5.1",
|
"paramiko==3.5.1",
|
||||||
|
"psutil==7.0.0",
|
||||||
"psycopg[c,pool]==3.2.9",
|
"psycopg[c,pool]==3.2.9",
|
||||||
"pydantic==2.11.7",
|
"pydantic==2.11.7",
|
||||||
"pydantic-scim==0.0.8",
|
"pydantic-scim==0.0.8",
|
||||||
|
|||||||
28
schema.yml
28
schema.yml
@@ -7006,6 +7006,34 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/enterprise/support_bundle/:
|
||||||
|
post:
|
||||||
|
operationId: enterprise_support_bundle_create
|
||||||
|
description: Generate a support bundle.
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/gzip:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
/events/events/:
|
/events/events/:
|
||||||
get:
|
get:
|
||||||
operationId: events_events_list
|
operationId: events_events_list
|
||||||
|
|||||||
17
uv.lock
generated
17
uv.lock
generated
@@ -211,6 +211,7 @@ dependencies = [
|
|||||||
{ name = "opencontainers" },
|
{ name = "opencontainers" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
{ name = "paramiko" },
|
{ name = "paramiko" },
|
||||||
|
{ name = "psutil" },
|
||||||
{ name = "psycopg", extra = ["c", "pool"] },
|
{ name = "psycopg", extra = ["c", "pool"] },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pydantic-scim" },
|
{ name = "pydantic-scim" },
|
||||||
@@ -310,6 +311,7 @@ requires-dist = [
|
|||||||
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
|
{ name = "opencontainers", git = "https://github.com/vsoch/oci-python?rev=ceb4fcc090851717a3069d78e85ceb1e86c2740c" },
|
||||||
{ name = "packaging", specifier = "==25.0" },
|
{ name = "packaging", specifier = "==25.0" },
|
||||||
{ name = "paramiko", specifier = "==3.5.1" },
|
{ name = "paramiko", specifier = "==3.5.1" },
|
||||||
|
{ name = "psutil", specifier = "==7.0.0" },
|
||||||
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.2.9" },
|
{ name = "psycopg", extras = ["c", "pool"], specifier = "==3.2.9" },
|
||||||
{ name = "pydantic", specifier = "==2.11.7" },
|
{ name = "pydantic", specifier = "==2.11.7" },
|
||||||
{ name = "pydantic-scim", specifier = "==0.0.8" },
|
{ name = "pydantic-scim", specifier = "==0.0.8" },
|
||||||
@@ -2411,6 +2413,21 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" },
|
{ url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psutil"
|
||||||
|
version = "7.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg"
|
name = "psycopg"
|
||||||
version = "3.2.9"
|
version = "3.2.9"
|
||||||
|
|||||||
@@ -261,6 +261,16 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
|||||||
>${msg("Go to Customer Portal")}</a
|
>${msg("Go to Customer Portal")}</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pf-c-card__body">
|
||||||
|
<ak-action-button
|
||||||
|
class="pf-m-secondary pf-m-block"
|
||||||
|
.apiRequest=${() => {
|
||||||
|
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseSupportBundleCreate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${msg("Create support bundle")}
|
||||||
|
</ak-action-button>
|
||||||
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<a target="_blank" href="https://docs.goauthentik.io/docs/enterprise/get-started"
|
<a target="_blank" href="https://docs.goauthentik.io/docs/enterprise/get-started"
|
||||||
>${msg("Learn more")}</a
|
>${msg("Learn more")}</a
|
||||||
|
|||||||
Reference in New Issue
Block a user