Compare commits
	
		
			32 Commits
		
	
	
		
			version/0.
			...
			version/0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e4b66d991c | |||
| 68adc2d5a5 | |||
| 349a3a67d5 | |||
| e1394207e7 | |||
| f265c1f10b | |||
| 1aecdc7f8f | |||
| a18edaf62b | |||
| c91abe448c | |||
| e531e52403 | |||
| cae536fa65 | |||
| 316b15b8a9 | |||
| e6ccd4fa76 | |||
| 86aabba3ed | |||
| 0b36aad5c8 | |||
| 64d2a216f0 | |||
| a5e5e140d6 | |||
| 29f98abd00 | |||
| 7b5ce4e98a | |||
| d7fa52ebf3 | |||
| 2ffaa94825 | |||
| b80b2626a6 | |||
| 3b7bba5a62 | |||
| 2d9efe035e | |||
| 48438e28fd | |||
| 885a2f0a58 | |||
| cf46ee06b7 | |||
| 9e33b49d29 | |||
| 1179ba4ef2 | |||
| 3c12c8b3ff | |||
| 4d22659b6e | |||
| 2c0709eeee | |||
| c24d1b6b84 | 
| @ -1,5 +1,5 @@ | ||||
| [bumpversion] | ||||
| current_version = 0.13.1-stable | ||||
| current_version = 0.13.2-stable | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | ||||
|  | ||||
							
								
								
									
										14
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @ -18,11 +18,11 @@ jobs: | ||||
|       - name: Building Docker Image | ||||
|         run: docker build | ||||
|           --no-cache | ||||
|           -t beryju/authentik:0.13.1-stable | ||||
|           -t beryju/authentik:0.13.2-stable | ||||
|           -t beryju/authentik:latest | ||||
|           -f Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik:0.13.1-stable | ||||
|         run: docker push beryju/authentik:0.13.2-stable | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik:latest | ||||
|   build-proxy: | ||||
| @ -48,11 +48,11 @@ jobs: | ||||
|           cd proxy/ | ||||
|           docker build \ | ||||
|           --no-cache \ | ||||
|           -t beryju/authentik-proxy:0.13.1-stable \ | ||||
|           -t beryju/authentik-proxy:0.13.2-stable \ | ||||
|           -t beryju/authentik-proxy:latest \ | ||||
|           -f Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik-proxy:0.13.1-stable | ||||
|         run: docker push beryju/authentik-proxy:0.13.2-stable | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik-proxy:latest | ||||
|   build-static: | ||||
| @ -69,11 +69,11 @@ jobs: | ||||
|           cd web/ | ||||
|           docker build \ | ||||
|           --no-cache \ | ||||
|           -t beryju/authentik-static:0.13.1-stable \ | ||||
|           -t beryju/authentik-static:0.13.2-stable \ | ||||
|           -t beryju/authentik-static:latest \ | ||||
|           -f Dockerfile . | ||||
|       - name: Push Docker Container to Registry (versioned) | ||||
|         run: docker push beryju/authentik-static:0.13.1-stable | ||||
|         run: docker push beryju/authentik-static:0.13.2-stable | ||||
|       - name: Push Docker Container to Registry (latest) | ||||
|         run: docker push beryju/authentik-static:latest | ||||
|   test-release: | ||||
| @ -107,5 +107,5 @@ jobs: | ||||
|           SENTRY_PROJECT: authentik | ||||
|           SENTRY_URL: https://sentry.beryju.org | ||||
|         with: | ||||
|           tagName: 0.13.1-stable | ||||
|           tagName: 0.13.2-stable | ||||
|           environment: beryjuorg-prod | ||||
|  | ||||
							
								
								
									
										36
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										36
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @ -74,18 +74,18 @@ | ||||
|         }, | ||||
|         "boto3": { | ||||
|             "hashes": [ | ||||
|                 "sha256:ad0e8dbd934d97b5228252785c0236c3ee4d464c14138f568e371bf43c6ea584", | ||||
|                 "sha256:ee86c26b3d457aa4d0256d0535d13107c32aa33bb5eb2a0b2dac9d81c3aca405" | ||||
|                 "sha256:18d7ba5d623d4794f439201ab900c9c14a50019bc52d9113b0a2bb2e1ef9af2c", | ||||
|                 "sha256:1ddfd307d409e7bc792bd12923078f59c2f56fbba4065c320b3f768481bbbbf7" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.16.37" | ||||
|             "version": "==1.16.38" | ||||
|         }, | ||||
|         "botocore": { | ||||
|             "hashes": [ | ||||
|                 "sha256:5605c250f6f7c72ca50e45eab6186dfda03cb84296ca5b05f7416defcd3fcbc5", | ||||
|                 "sha256:67bf1285455d79336ce7061da1768206b78f7a0efc13c8b4033fd348a74e7491" | ||||
|                 "sha256:1f1ecb1b0c6ffc8fcdd5eeb40f33e986dfe9724dc66c83017014a0506af6378a", | ||||
|                 "sha256:38ccc132c5b9d1e7a4dd37af78061fd2dd0e4fd611f527b409a4e9a679a85cdb" | ||||
|             ], | ||||
|             "version": "==1.19.37" | ||||
|             "version": "==1.19.38" | ||||
|         }, | ||||
|         "cachetools": { | ||||
|             "hashes": [ | ||||
| @ -96,11 +96,11 @@ | ||||
|         }, | ||||
|         "celery": { | ||||
|             "hashes": [ | ||||
|                 "sha256:45bb7909061862305cefec94289fabc1b89ac004680f4dc7d9dea642a2507e53", | ||||
|                 "sha256:533f3635065b7ed362ffc04228635b4c82d53a9ab812118ccdedb5eae281fb97" | ||||
|                 "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13", | ||||
|                 "sha256:f4efebe6f8629b0da2b8e529424de376494f5b7a743c321c8a2ddc2b1414921c" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==5.0.4" | ||||
|             "version": "==5.0.5" | ||||
|         }, | ||||
|         "certifi": { | ||||
|             "hashes": [ | ||||
| @ -168,10 +168,10 @@ | ||||
|         }, | ||||
|         "chardet": { | ||||
|             "hashes": [ | ||||
|                 "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", | ||||
|                 "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" | ||||
|                 "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", | ||||
|                 "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" | ||||
|             ], | ||||
|             "version": "==3.0.4" | ||||
|             "version": "==4.0.0" | ||||
|         }, | ||||
|         "click": { | ||||
|             "hashes": [ | ||||
| @ -343,11 +343,11 @@ | ||||
|         }, | ||||
|         "django-storages": { | ||||
|             "hashes": [ | ||||
|                 "sha256:12de8fb2605b9b57bfaf54b075280d7cbb3b3ee1ca4bc9b9add147af87fe3a2c", | ||||
|                 "sha256:652275ab7844538c462b62810276c0244866f345878256a9e0e86f5b1283ae18" | ||||
|                 "sha256:056ec3e9e2b0c6f363913976072ffba2923e79e4859578047da139ba1637497e", | ||||
|                 "sha256:7af56611c62a1c174aab4e862efb7fdd98296dccf76f42135f5b6851fc313c97" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.10.1" | ||||
|             "version": "==1.11" | ||||
|         }, | ||||
|         "djangorestframework": { | ||||
|             "hashes": [ | ||||
| @ -950,10 +950,10 @@ | ||||
|         }, | ||||
|         "requests": { | ||||
|             "hashes": [ | ||||
|                 "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8", | ||||
|                 "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998" | ||||
|                 "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", | ||||
|                 "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" | ||||
|             ], | ||||
|             "version": "==2.25.0" | ||||
|             "version": "==2.25.1" | ||||
|         }, | ||||
|         "requests-oauthlib": { | ||||
|             "hashes": [ | ||||
|  | ||||
| @ -1,2 +1,2 @@ | ||||
| """authentik""" | ||||
| __version__ = "0.13.1-stable" | ||||
| __version__ = "0.13.2-stable" | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| """authentik administration overview""" | ||||
| """authentik administration metrics""" | ||||
| import time | ||||
| from collections import Counter | ||||
| from datetime import timedelta | ||||
| @ -47,7 +47,7 @@ def get_events_per_1h(**filter_kwargs) -> List[Dict[str, int]]: | ||||
| 
 | ||||
| 
 | ||||
| class AdministrationMetricsSerializer(Serializer): | ||||
|     """Overview View""" | ||||
|     """Login Metrics per 1h""" | ||||
| 
 | ||||
|     logins_per_1h = SerializerMethodField() | ||||
|     logins_failed_per_1h = SerializerMethodField() | ||||
| @ -68,12 +68,12 @@ class AdministrationMetricsSerializer(Serializer): | ||||
| 
 | ||||
| 
 | ||||
| class AdministrationMetricsViewSet(ViewSet): | ||||
|     """Return single instance of AdministrationMetricsSerializer""" | ||||
|     """Login Metrics per 1h""" | ||||
| 
 | ||||
|     permission_classes = [IsAdminUser] | ||||
| 
 | ||||
|     @swagger_auto_schema(responses={200: AdministrationMetricsSerializer(many=True)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Return single instance of AdministrationMetricsSerializer""" | ||||
|         """Login Metrics per 1h""" | ||||
|         serializer = AdministrationMetricsSerializer(True) | ||||
|         return Response(serializer.data) | ||||
| @ -1,79 +0,0 @@ | ||||
| """authentik administration overview""" | ||||
| from django.core.cache import cache | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from rest_framework.fields import SerializerMethodField | ||||
| from rest_framework.permissions import IsAdminUser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import Serializer | ||||
| from rest_framework.viewsets import ViewSet | ||||
|  | ||||
| from authentik import __version__ | ||||
| from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version | ||||
| from authentik.core.models import Provider | ||||
| from authentik.policies.models import Policy | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
|  | ||||
| class AdministrationOverviewSerializer(Serializer): | ||||
|     """Overview View""" | ||||
|  | ||||
|     version = SerializerMethodField() | ||||
|     version_latest = SerializerMethodField() | ||||
|     worker_count = SerializerMethodField() | ||||
|     providers_without_application = SerializerMethodField() | ||||
|     policies_without_binding = SerializerMethodField() | ||||
|     cached_policies = SerializerMethodField() | ||||
|     cached_flows = SerializerMethodField() | ||||
|  | ||||
|     def get_version(self, _) -> str: | ||||
|         """Get current version""" | ||||
|         return __version__ | ||||
|  | ||||
|     def get_version_latest(self, _) -> str: | ||||
|         """Get latest version from cache""" | ||||
|         version_in_cache = cache.get(VERSION_CACHE_KEY) | ||||
|         if not version_in_cache: | ||||
|             update_latest_version.delay() | ||||
|             return __version__ | ||||
|         return version_in_cache | ||||
|  | ||||
|     def get_worker_count(self, _) -> int: | ||||
|         """Ping workers""" | ||||
|         return len(CELERY_APP.control.ping(timeout=0.5)) | ||||
|  | ||||
|     def get_providers_without_application(self, _) -> int: | ||||
|         """Count of providers without application""" | ||||
|         return len(Provider.objects.filter(application=None)) | ||||
|  | ||||
|     def get_policies_without_binding(self, _) -> int: | ||||
|         """Count of policies not bound or use in prompt stages""" | ||||
|         return len( | ||||
|             Policy.objects.filter(bindings__isnull=True, promptstage__isnull=True) | ||||
|         ) | ||||
|  | ||||
|     def get_cached_policies(self, _) -> int: | ||||
|         """Get cached policy count""" | ||||
|         return len(cache.keys("policy_*")) | ||||
|  | ||||
|     def get_cached_flows(self, _) -> int: | ||||
|         """Get cached flow count""" | ||||
|         return len(cache.keys("flow_*")) | ||||
|  | ||||
|     def create(self, request: Request) -> Response: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, request: Request) -> Response: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class AdministrationOverviewViewSet(ViewSet): | ||||
|     """Return single instance of AdministrationOverviewSerializer""" | ||||
|  | ||||
|     permission_classes = [IsAdminUser] | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: AdministrationOverviewSerializer(many=True)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Return single instance of AdministrationOverviewSerializer""" | ||||
|         serializer = AdministrationOverviewSerializer(True) | ||||
|         return Response(serializer.data) | ||||
| @ -66,7 +66,7 @@ class TaskViewSet(ViewSet): | ||||
|                     "successful": True, | ||||
|                 } | ||||
|             ) | ||||
|         except ImportError: | ||||
|         except ImportError:  # pragma: no cover | ||||
|             # if we get an import error, the module path has probably changed | ||||
|             task.delete() | ||||
|             return Response({"successful": False}) | ||||
|  | ||||
							
								
								
									
										60
									
								
								authentik/admin/api/version.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								authentik/admin/api/version.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | ||||
| """authentik administration overview""" | ||||
| from django.core.cache import cache | ||||
| from drf_yasg2.utils import swagger_auto_schema | ||||
| from packaging.version import parse | ||||
| from rest_framework.fields import SerializerMethodField | ||||
| from rest_framework.mixins import ListModelMixin | ||||
| from rest_framework.permissions import IsAdminUser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import Serializer | ||||
| from rest_framework.viewsets import GenericViewSet | ||||
|  | ||||
| from authentik import __version__ | ||||
| from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version | ||||
|  | ||||
|  | ||||
| class VersionSerializer(Serializer): | ||||
|     """Get running and latest version.""" | ||||
|  | ||||
|     version_current = SerializerMethodField() | ||||
|     version_latest = SerializerMethodField() | ||||
|     outdated = SerializerMethodField() | ||||
|  | ||||
|     def get_version_current(self, _) -> str: | ||||
|         """Get current version""" | ||||
|         return __version__ | ||||
|  | ||||
|     def get_version_latest(self, _) -> str: | ||||
|         """Get latest version from cache""" | ||||
|         version_in_cache = cache.get(VERSION_CACHE_KEY) | ||||
|         if not version_in_cache:  # pragma: no cover | ||||
|             update_latest_version.delay() | ||||
|             return __version__ | ||||
|         return version_in_cache | ||||
|  | ||||
|     def get_outdated(self, instance) -> bool: | ||||
|         """Check if we're running the latest version""" | ||||
|         return parse(self.get_version_current(instance)) < parse( | ||||
|             self.get_version_latest(instance) | ||||
|         ) | ||||
|  | ||||
|     def create(self, request: Request) -> Response: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def update(self, request: Request) -> Response: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|  | ||||
| class VersionViewSet(ListModelMixin, GenericViewSet): | ||||
|     """Get running and latest version.""" | ||||
|  | ||||
|     permission_classes = [IsAdminUser] | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return None | ||||
|  | ||||
|     @swagger_auto_schema(responses={200: VersionSerializer(many=True)}) | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Get running and latest version.""" | ||||
|         return Response(VersionSerializer(True).data) | ||||
							
								
								
									
										25
									
								
								authentik/admin/api/workers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								authentik/admin/api/workers.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| """authentik administration overview""" | ||||
| from rest_framework.mixins import ListModelMixin | ||||
| from rest_framework.permissions import IsAdminUser | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import Serializer | ||||
| from rest_framework.viewsets import GenericViewSet | ||||
|  | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
|  | ||||
| class WorkerViewSet(ListModelMixin, GenericViewSet): | ||||
|     """Get currently connected worker count.""" | ||||
|  | ||||
|     serializer_class = Serializer | ||||
|     permission_classes = [IsAdminUser] | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return None | ||||
|  | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Get currently connected worker count.""" | ||||
|         return Response( | ||||
|             {"pagination": {"count": len(CELERY_APP.control.ping(timeout=0.5))}} | ||||
|         ) | ||||
| @ -1,230 +0,0 @@ | ||||
| {% extends "administration/base.html" %} | ||||
|  | ||||
| {% load i18n %} | ||||
| {% load static %} | ||||
|  | ||||
| {% block content %} | ||||
| <section class="pf-c-page__main-section pf-m-light"> | ||||
|     <div class="pf-c-content"> | ||||
|         <h1>{% trans 'System Overview' %}</h1> | ||||
|     </div> | ||||
| </section> | ||||
| <section class="pf-c-page__main-section"> | ||||
|     <div class="pf-l-gallery pf-m-gutter"> | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col" style="grid-column-end: span 3;grid-row-end: span 2;"> | ||||
|             <div class="pf-c-card__header"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-server"></i> {% trans 'Logins over the last 24 hours' %} | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 <ak-admin-logins-chart url="{% url 'authentik_api:admin_metrics-list' %}"></ak-admin-logins-chart> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col" style="grid-column-end: span 2;grid-row-end: span 3;"> | ||||
|             <div class="pf-c-card__header"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-server"></i> {% trans 'Apps with most usage' %} | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 <table class="pf-c-table pf-m-compact" role="grid"> | ||||
|                     <thead> | ||||
|                         <tr role="row"> | ||||
|                             <th role="columnheader" scope="col">{% trans 'Application' %}</th> | ||||
|                             <th role="columnheader" scope="col">{% trans 'Logins' %}</th> | ||||
|                             <th role="columnheader" scope="col"></th> | ||||
|                         </tr> | ||||
|                     </thead> | ||||
|                     <tbody role="rowgroup"> | ||||
|                         {% for app in most_used_applications %} | ||||
|                         <tr role="row"> | ||||
|                             <td role="cell"> | ||||
|                                 {{ app.application.name }} | ||||
|                             </td> | ||||
|                             <td role="cell"> | ||||
|                                 {{ app.total_logins }} | ||||
|                             </td> | ||||
|                             <td role="cell"> | ||||
|                                 <progress value="{{ app.total_logins }}" max="{{ most_used_applications.0.total_logins }}"></progress> | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                         {% endfor %} | ||||
|                     </tbody> | ||||
|                 </table> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact"> | ||||
|             <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %} | ||||
|                 </div> | ||||
|                 <a href="{% url 'authentik_admin:providers' %}"> | ||||
|                     <i class="fa fa-external-link-alt"> </i> | ||||
|                 </a> | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 {% if providers_without_application.exists %} | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <i class="fa fa-exclamation-triangle"></i> {{ provider_count }} | ||||
|                 </p> | ||||
|                 <p>{% trans 'Warning: At least one Provider has no application assigned.' %}</p> | ||||
|                 {% else %} | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <i class="fa fa-check-circle"></i> {{ provider_count }} | ||||
|                 </p> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact"> | ||||
|             <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %} | ||||
|                 </div> | ||||
|                 <a href="{% url 'authentik_admin:policies' %}"> | ||||
|                     <i class="fa fa-external-link-alt"> </i> | ||||
|                 </a> | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 {% if policies_without_binding %} | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <i class="fa fa-exclamation-triangle"></i> {{ policy_count }} | ||||
|                 </p> | ||||
|                 <p>{% trans 'Policies without binding exist.' %}</p> | ||||
|                 {% else %} | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <i class="fa fa-check-circle"></i> {{ policy_count }} | ||||
|                 </p> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact"> | ||||
|             <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-user"></i> {% trans 'Users' %} | ||||
|                 </div> | ||||
|                 <a href="{% url 'authentik_admin:users' %}"> | ||||
|                     <i class="fa fa-external-link-alt"> </i> | ||||
|                 </a> | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <i class="fa fa-check-circle"></i> {{ user_count }} | ||||
|                 </p> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact"> | ||||
|             <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %} | ||||
|                 </div> | ||||
|                 <a href="https://github.com/BeryJu/authentik/releases" target="_blank"> | ||||
|                     <i class="fa fa-external-link-alt"> </i> | ||||
|                 </a> | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     {% if version >= version_latest %} | ||||
|                     <i class="fa fa-check-circle"></i> {{ version }} | ||||
|                     {% else %} | ||||
|                     <i class="fa fa-exclamation-triangle"></i> {{ version }} | ||||
|                     {% endif %} | ||||
|                 </p> | ||||
|                 {% if version >= version_latest %} | ||||
|                     {% blocktrans %} | ||||
|                     Up-to-date! | ||||
|                     {% endblocktrans %} | ||||
|                 {% else %} | ||||
|                     {% blocktrans with latest=version_latest %} | ||||
|                     {{ latest }} is available! | ||||
|                     {% endblocktrans %} | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact"> | ||||
|             <div class="pf-c-card__header"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-server"></i> {% trans 'Workers' %} | ||||
|                 </div> | ||||
|             </div> | ||||
|             <fetch-fill-slot class="pf-c-card__body" url="{% url 'authentik_api:admin_overview-list' %}" key="worker_count"> | ||||
|                 <div slot="value < 1"> | ||||
|                     <p class="ak-aggregate-card"> | ||||
|                         <i class="fa fa-exclamation-triangle"></i> <span data-value></span> | ||||
|                     </p> | ||||
|                     <p>{% trans 'No workers connected.' %}</p> | ||||
|                 </div> | ||||
|                 <div slot="value >= 1"> | ||||
|                     <p class="ak-aggregate-card"> | ||||
|                         <i class="fa fa-check-circle"></i> <span data-value></span> | ||||
|                     </p> | ||||
|                 </div> | ||||
|                 <div> | ||||
|                     <span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading..."> | ||||
|                         <span class="pf-c-spinner__clipper"></span> | ||||
|                         <span class="pf-c-spinner__lead-ball"></span> | ||||
|                         <span class="pf-c-spinner__tail-ball"></span> | ||||
|                     </span> | ||||
|                 </div> | ||||
|             </fetch-fill-slot> | ||||
|         </div> | ||||
|  | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact"> | ||||
|             <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %} | ||||
|                 </div> | ||||
|                 <ak-modal-button href="{% url 'authentik_admin:overview-clear-policy-cache' %}"> | ||||
|                     <a slot="trigger"> | ||||
|                         <i class="fa fa-trash"> </i> | ||||
|                     </a> | ||||
|                     <div slot="modal"></div> | ||||
|                 </ak-modal-button> | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 {% if cached_policies < 1 %} | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <i class="fa fa-exclamation-triangle"></i> {{ cached_policies }} | ||||
|                 </p> | ||||
|                 <p>{% trans 'No policies cached. Users may experience slow response times.' %}</p> | ||||
|                 {% else %} | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <i class="fa fa-check-circle"></i> {{ cached_policies }} | ||||
|                 </p> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact"> | ||||
|             <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="pf-icon pf-icon-server"></i> {% trans 'Cached Flows' %} | ||||
|                 </div> | ||||
|                 <ak-modal-button href="{% url 'authentik_admin:overview-clear-flow-cache' %}"> | ||||
|                     <a slot="trigger"> | ||||
|                         <i class="fa fa-trash"> </i> | ||||
|                     </a> | ||||
|                     <div slot="modal"></div> | ||||
|                 </ak-modal-button> | ||||
|             </div> | ||||
|             <div class="pf-c-card__body"> | ||||
|                 {% if cached_flows < 1 %} | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <span class="fa fa-exclamation-triangle"></span> {{ cached_flows }} | ||||
|                 </p> | ||||
|                 <p>{% trans 'No flows cached.' %}</p> | ||||
|                 {% else %} | ||||
|                 <p class="ak-aggregate-card"> | ||||
|                     <i class="fa fa-check-circle"></i> {{ cached_flows }} | ||||
|                 </p> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </section> | ||||
| {% endblock %} | ||||
| @ -6,6 +6,7 @@ from django.test import TestCase | ||||
|  | ||||
| from authentik import __version__ | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.core.tasks import clean_expired_models | ||||
|  | ||||
|  | ||||
| class TestAdminAPI(TestCase): | ||||
| @ -19,19 +20,54 @@ class TestAdminAPI(TestCase): | ||||
|         self.group.save() | ||||
|         self.client.force_login(self.user) | ||||
|  | ||||
|     def test_overview(self): | ||||
|         """Test Overview API""" | ||||
|         response = self.client.get(reverse("authentik_api:admin_overview-list")) | ||||
|     def test_tasks(self): | ||||
|         """Test Task API""" | ||||
|         clean_expired_models.delay() | ||||
|         response = self.client.get(reverse("authentik_api:admin_system_tasks-list")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         body = loads(response.content) | ||||
|         self.assertEqual(body["version"], __version__) | ||||
|         self.assertTrue( | ||||
|             any([task["task_name"] == "clean_expired_models" for task in body]) | ||||
|         ) | ||||
|  | ||||
|     def test_tasks_retry(self): | ||||
|         """Test Task API (retry)""" | ||||
|         clean_expired_models.delay() | ||||
|         response = self.client.post( | ||||
|             reverse( | ||||
|                 "authentik_api:admin_system_tasks-retry", | ||||
|                 kwargs={"pk": "clean_expired_models"}, | ||||
|             ) | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         body = loads(response.content) | ||||
|         self.assertTrue(body["successful"]) | ||||
|  | ||||
|     def test_tasks_retry_404(self): | ||||
|         """Test Task API (retry, 404)""" | ||||
|         response = self.client.post( | ||||
|             reverse( | ||||
|                 "authentik_api:admin_system_tasks-retry", | ||||
|                 kwargs={"pk": "qwerqewrqrqewrqewr"}, | ||||
|             ) | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 404) | ||||
|  | ||||
|     def test_version(self): | ||||
|         """Test Version API""" | ||||
|         response = self.client.get(reverse("authentik_api:admin_version-list")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         body = loads(response.content) | ||||
|         self.assertEqual(body["version_current"], __version__) | ||||
|  | ||||
|     def test_workers(self): | ||||
|         """Test Workers API""" | ||||
|         response = self.client.get(reverse("authentik_api:admin_workers-list")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         body = loads(response.content) | ||||
|         self.assertEqual(body["pagination"]["count"], 0) | ||||
|  | ||||
|     def test_metrics(self): | ||||
|         """Test metrics API""" | ||||
|         response = self.client.get(reverse("authentik_api:admin_metrics-list")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
|     def test_tasks(self): | ||||
|         """Test tasks metrics API""" | ||||
|         response = self.client.get(reverse("authentik_api:admin_system_tasks-list")) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|  | ||||
| @ -34,7 +34,6 @@ urlpatterns = [ | ||||
|         overview.PolicyCacheClearView.as_view(), | ||||
|         name="overview-clear-policy-cache", | ||||
|     ), | ||||
|     path("overview/", overview.AdministrationOverviewView.as_view(), name="overview"), | ||||
|     # Applications | ||||
|     path( | ||||
|         "applications/", applications.ApplicationListView.as_view(), name="applications" | ||||
|  | ||||
| @ -1,65 +1,25 @@ | ||||
| """authentik administration overview""" | ||||
| from typing import Union | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.contrib.messages.views import SuccessMessageMixin | ||||
| from django.core.cache import cache | ||||
| from django.http.request import HttpRequest | ||||
| from django.http.response import HttpResponse | ||||
| from django.urls import reverse_lazy | ||||
| from django.utils.translation import gettext as _ | ||||
| from django.views.generic import FormView, TemplateView | ||||
| from packaging.version import LegacyVersion, Version, parse | ||||
| from django.views.generic import FormView | ||||
| from structlog import get_logger | ||||
|  | ||||
| from authentik import __version__ | ||||
| from authentik.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm | ||||
| from authentik.admin.mixins import AdminRequiredMixin | ||||
| from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version | ||||
| from authentik.core.models import Provider, User | ||||
| from authentik.policies.models import Policy | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class AdministrationOverviewView(AdminRequiredMixin, TemplateView): | ||||
|     """Overview View""" | ||||
|  | ||||
|     template_name = "administration/overview.html" | ||||
|  | ||||
|     def get_latest_version(self) -> Union[LegacyVersion, Version]: | ||||
|         """Get latest version from cache""" | ||||
|         version_in_cache = cache.get(VERSION_CACHE_KEY) | ||||
|         if not version_in_cache: | ||||
|             if not settings.DEBUG: | ||||
|                 update_latest_version.delay() | ||||
|             return parse(__version__) | ||||
|         return parse(version_in_cache) | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         kwargs["policy_count"] = len(Policy.objects.all()) | ||||
|         kwargs["user_count"] = len(User.objects.all()) - 1  # Remove anonymous user | ||||
|         kwargs["provider_count"] = len(Provider.objects.all()) | ||||
|         kwargs["version"] = parse(__version__) | ||||
|         kwargs["version_latest"] = self.get_latest_version() | ||||
|         kwargs["providers_without_application"] = Provider.objects.filter( | ||||
|             application=None | ||||
|         ) | ||||
|         kwargs["policies_without_binding"] = len( | ||||
|             Policy.objects.filter(bindings__isnull=True, promptstage__isnull=True) | ||||
|         ) | ||||
|         kwargs["cached_policies"] = len(cache.keys("policy_*")) | ||||
|         kwargs["cached_flows"] = len(cache.keys("flow_*")) | ||||
|         return super().get_context_data(**kwargs) | ||||
|  | ||||
|  | ||||
| class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): | ||||
|     """View to clear Policy cache""" | ||||
|  | ||||
|     form_class = PolicyCacheClearForm | ||||
|  | ||||
|     template_name = "generic/form_non_model.html" | ||||
|     success_url = reverse_lazy("authentik_admin:overview") | ||||
|     success_url = "/" | ||||
|     success_message = _("Successfully cleared Policy cache") | ||||
|  | ||||
|     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||
| @ -75,7 +35,7 @@ class FlowCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView): | ||||
|     form_class = FlowCacheClearForm | ||||
|  | ||||
|     template_name = "generic/form_non_model.html" | ||||
|     success_url = reverse_lazy("authentik_admin:overview") | ||||
|     success_url = "/" | ||||
|     success_message = _("Successfully cleared Flow cache") | ||||
|  | ||||
|     def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: | ||||
|  | ||||
| @ -5,9 +5,10 @@ from drf_yasg2.views import get_schema_view | ||||
| from rest_framework import routers | ||||
| from rest_framework.permissions import AllowAny | ||||
|  | ||||
| from authentik.admin.api.overview import AdministrationOverviewViewSet | ||||
| from authentik.admin.api.overview_metrics import AdministrationMetricsViewSet | ||||
| from authentik.admin.api.metrics import AdministrationMetricsViewSet | ||||
| from authentik.admin.api.tasks import TaskViewSet | ||||
| from authentik.admin.api.version import VersionViewSet | ||||
| from authentik.admin.api.workers import WorkerViewSet | ||||
| from authentik.api.v2.config import ConfigsViewSet | ||||
| from authentik.api.v2.messages import MessagesViewSet | ||||
| from authentik.audit.api import EventViewSet | ||||
| @ -19,13 +20,22 @@ from authentik.core.api.sources import SourceViewSet | ||||
| from authentik.core.api.tokens import TokenViewSet | ||||
| from authentik.core.api.users import UserViewSet | ||||
| from authentik.crypto.api import CertificateKeyPairViewSet | ||||
| from authentik.flows.api import FlowStageBindingViewSet, FlowViewSet, StageViewSet | ||||
| from authentik.flows.api import ( | ||||
|     FlowCacheViewSet, | ||||
|     FlowStageBindingViewSet, | ||||
|     FlowViewSet, | ||||
|     StageViewSet, | ||||
| ) | ||||
| from authentik.outposts.api import ( | ||||
|     DockerServiceConnectionViewSet, | ||||
|     KubernetesServiceConnectionViewSet, | ||||
|     OutpostViewSet, | ||||
| ) | ||||
| from authentik.policies.api import PolicyBindingViewSet, PolicyViewSet | ||||
| from authentik.policies.api import ( | ||||
|     PolicyBindingViewSet, | ||||
|     PolicyCacheViewSet, | ||||
|     PolicyViewSet, | ||||
| ) | ||||
| from authentik.policies.dummy.api import DummyPolicyViewSet | ||||
| from authentik.policies.expiry.api import PasswordExpiryPolicyViewSet | ||||
| from authentik.policies.expression.api import ExpressionPolicyViewSet | ||||
| @ -63,9 +73,8 @@ router = routers.DefaultRouter() | ||||
| router.register("root/messages", MessagesViewSet, basename="messages") | ||||
| router.register("root/config", ConfigsViewSet, basename="configs") | ||||
|  | ||||
| router.register( | ||||
|     "admin/overview", AdministrationOverviewViewSet, basename="admin_overview" | ||||
| ) | ||||
| router.register("admin/version", VersionViewSet, basename="admin_version") | ||||
| router.register("admin/workers", WorkerViewSet, basename="admin_workers") | ||||
| router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics") | ||||
| router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks") | ||||
|  | ||||
| @ -82,6 +91,7 @@ router.register( | ||||
| router.register("outposts/proxy", ProxyOutpostConfigViewSet) | ||||
|  | ||||
| router.register("flows/instances", FlowViewSet) | ||||
| router.register("flows/cached", FlowCacheViewSet, basename="flows_cache") | ||||
| router.register("flows/bindings", FlowStageBindingViewSet) | ||||
|  | ||||
| router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet) | ||||
| @ -94,6 +104,7 @@ router.register("sources/saml", SAMLSourceViewSet) | ||||
| router.register("sources/oauth", OAuthSourceViewSet) | ||||
|  | ||||
| router.register("policies/all", PolicyViewSet) | ||||
| router.register("policies/cached", PolicyCacheViewSet, basename="policies_cache") | ||||
| router.register("policies/bindings", PolicyBindingViewSet) | ||||
| router.register("policies/expression", ExpressionPolicyViewSet) | ||||
| router.register("policies/group_membership", GroupMembershipPolicyViewSet) | ||||
|  | ||||
| @ -11,7 +11,7 @@ from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from rest_framework_guardian.filters import ObjectPermissionsFilter | ||||
|  | ||||
| from authentik.admin.api.overview_metrics import get_events_per_1h | ||||
| from authentik.admin.api.metrics import get_events_per_1h | ||||
| from authentik.audit.models import EventAction | ||||
| from authentik.core.models import Application | ||||
| from authentik.policies.engine import PolicyEngine | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| """Provider API Views""" | ||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
|  | ||||
| from authentik.core.models import Provider | ||||
|  | ||||
| @ -14,17 +14,33 @@ class ProviderSerializer(ModelSerializer): | ||||
|         """Get object type so that we know which API Endpoint to use to get the full object""" | ||||
|         return obj._meta.object_name.lower().replace("provider", "") | ||||
|  | ||||
|     def to_representation(self, instance: Provider): | ||||
|         # pyright: reportGeneralTypeIssues=false | ||||
|         if instance.__class__ == Provider: | ||||
|             return super().to_representation(instance) | ||||
|         return instance.serializer(instance=instance).data | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = Provider | ||||
|         fields = ["pk", "name", "authorization_flow", "property_mappings", "__type__"] | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "name", | ||||
|             "application", | ||||
|             "authorization_flow", | ||||
|             "property_mappings", | ||||
|             "__type__", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class ProviderViewSet(ReadOnlyModelViewSet): | ||||
| class ProviderViewSet(ModelViewSet): | ||||
|     """Provider Viewset""" | ||||
|  | ||||
|     queryset = Provider.objects.all() | ||||
|     serializer_class = ProviderSerializer | ||||
|     filterset_fields = { | ||||
|         "application": ["isnull"], | ||||
|     } | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return Provider.objects.select_subclasses() | ||||
|  | ||||
| @ -14,6 +14,7 @@ from django.utils.timezone import now | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from guardian.mixins import GuardianUserMixin | ||||
| from model_utils.managers import InheritanceManager | ||||
| from rest_framework.serializers import Serializer | ||||
| from structlog import get_logger | ||||
|  | ||||
| from authentik.core.exceptions import PropertyMappingExpressionException | ||||
| @ -127,7 +128,7 @@ class User(GuardianUserMixin, AbstractUser): | ||||
|         verbose_name_plural = _("Users") | ||||
|  | ||||
|  | ||||
| class Provider(models.Model): | ||||
| class Provider(SerializerModel): | ||||
|     """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" | ||||
|  | ||||
|     name = models.TextField() | ||||
| @ -156,6 +157,11 @@ class Provider(models.Model): | ||||
|         """Return Form class used to edit this object""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> Type[Serializer]: | ||||
|         """Get serializer for this model""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|  | ||||
| @ -1,7 +1,14 @@ | ||||
| """Flow API Views""" | ||||
| from django.core.cache import cache | ||||
| from rest_framework.serializers import ModelSerializer, SerializerMethodField | ||||
| from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet | ||||
| from rest_framework.mixins import ListModelMixin | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ( | ||||
|     ModelSerializer, | ||||
|     Serializer, | ||||
|     SerializerMethodField, | ||||
| ) | ||||
| from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet | ||||
|  | ||||
| from authentik.flows.models import Flow, FlowStageBinding, Stage | ||||
| from authentik.flows.planner import cache_key | ||||
| @ -98,3 +105,14 @@ class FlowStageBindingViewSet(ModelViewSet): | ||||
|     queryset = FlowStageBinding.objects.all() | ||||
|     serializer_class = FlowStageBindingSerializer | ||||
|     filterset_fields = "__all__" | ||||
|  | ||||
|  | ||||
| class FlowCacheViewSet(ListModelMixin, GenericViewSet): | ||||
|     """Info about cached flows""" | ||||
|  | ||||
|     queryset = Flow.objects.none() | ||||
|     serializer_class = Serializer | ||||
|  | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Info about cached flows""" | ||||
|         return Response(data={"pagination": {"count": len(cache.keys("flow_*"))}}) | ||||
|  | ||||
| @ -8,7 +8,7 @@ from django.test.client import RequestFactory | ||||
| from django.utils.encoding import force_str | ||||
|  | ||||
| from authentik.core.models import User | ||||
| from authentik.flows.exceptions import EmptyFlowException, FlowNonApplicableException | ||||
| from authentik.flows.exceptions import FlowNonApplicableException | ||||
| from authentik.flows.markers import ReevaluateMarker, StageMarker | ||||
| from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding | ||||
| from authentik.flows.planner import FlowPlan, FlowPlanner | ||||
| @ -40,6 +40,10 @@ class TestFlowExecutor(TestCase): | ||||
|     def setUp(self): | ||||
|         self.request_factory = RequestFactory() | ||||
|  | ||||
|     @patch( | ||||
|         "authentik.flows.views.to_stage_response", | ||||
|         TO_STAGE_RESPONSE_MOCK, | ||||
|     ) | ||||
|     def test_existing_plan_diff_flow(self): | ||||
|         """Check that a plan for a different flow cancels the current plan""" | ||||
|         flow = Flow.objects.create( | ||||
| @ -62,7 +66,7 @@ class TestFlowExecutor(TestCase): | ||||
|                     "authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug} | ||||
|                 ), | ||||
|             ) | ||||
|             self.assertEqual(response.status_code, 200) | ||||
|             self.assertEqual(response.status_code, 302) | ||||
|             self.assertEqual(cancel_mock.call_count, 2) | ||||
|  | ||||
|     @patch( | ||||
| @ -105,10 +109,13 @@ class TestFlowExecutor(TestCase): | ||||
|         response = self.client.get( | ||||
|             reverse("authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug}), | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertIsInstance(response, AccessDeniedResponse) | ||||
|         self.assertInHTML(EmptyFlowException.__doc__, response.rendered_content) | ||||
|         self.assertEqual(response.status_code, 302) | ||||
|         self.assertEqual(response.url, reverse("authentik_core:shell")) | ||||
|  | ||||
|     @patch( | ||||
|         "authentik.flows.views.to_stage_response", | ||||
|         TO_STAGE_RESPONSE_MOCK, | ||||
|     ) | ||||
|     def test_invalid_flow_redirect(self): | ||||
|         """Tests that an invalid flow still redirects""" | ||||
|         flow = Flow.objects.create( | ||||
| @ -121,11 +128,8 @@ class TestFlowExecutor(TestCase): | ||||
|         dest = "/unique-string" | ||||
|         url = reverse("authentik_flows:flow-executor", kwargs={"flow_slug": flow.slug}) | ||||
|         response = self.client.get(url + f"?{NEXT_ARG_NAME}={dest}") | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertJSONEqual( | ||||
|             force_str(response.content), | ||||
|             {"type": "redirect", "to": dest}, | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 302) | ||||
|         self.assertEqual(response.url, reverse("authentik_core:shell")) | ||||
|  | ||||
|     def test_multi_stage_flow(self): | ||||
|         """Test a full flow with multiple stages""" | ||||
| @ -161,6 +165,10 @@ class TestFlowExecutor(TestCase): | ||||
|         plan: FlowPlan = session[SESSION_KEY_PLAN] | ||||
|         self.assertEqual(len(plan.stages), 1) | ||||
|  | ||||
|     @patch( | ||||
|         "authentik.flows.views.to_stage_response", | ||||
|         TO_STAGE_RESPONSE_MOCK, | ||||
|     ) | ||||
|     def test_reevaluate_remove_last(self): | ||||
|         """Test planner with re-evaluate (last stage is removed)""" | ||||
|         flow = Flow.objects.create( | ||||
|  | ||||
| @ -83,7 +83,9 @@ class FlowExecutorView(View): | ||||
|                 return to_stage_response(self.request, self.handle_invalid_flow(exc)) | ||||
|             except EmptyFlowException as exc: | ||||
|                 LOGGER.warning("f(exec): Flow is empty", exc=exc) | ||||
|                 return to_stage_response(self.request, self.handle_invalid_flow(exc)) | ||||
|                 # To match behaviour with loading an empty flow plan from cache, | ||||
|                 # we don't show an error message here, but rather call _flow_done() | ||||
|                 return self._flow_done() | ||||
|         # We don't save the Plan after getting the next stage | ||||
|         # as it hasn't been successfully passed yet | ||||
|         next_stage = self.plan.next(self.request) | ||||
| @ -147,7 +149,7 @@ class FlowExecutorView(View): | ||||
|             NEXT_ARG_NAME, "authentik_core:shell" | ||||
|         ) | ||||
|         self.cancel() | ||||
|         return redirect_with_qs(next_param) | ||||
|         return to_stage_response(self.request, redirect_with_qs(next_param)) | ||||
|  | ||||
|     def stage_ok(self) -> HttpResponse: | ||||
|         """Callback called by stages upon successful completion. | ||||
|  | ||||
| @ -42,9 +42,8 @@ def outpost_service_connection_state(connection_pk: Any): | ||||
|         .select_subclasses() | ||||
|         .first() | ||||
|     ) | ||||
|     cache.delete(f"outpost_service_connection_{connection.pk.hex}") | ||||
|     state = connection.fetch_state() | ||||
|     cache.set(connection.state_key, state, timeout=0) | ||||
|     cache.set(connection.state_key, state, timeout=None) | ||||
|  | ||||
|  | ||||
| @CELERY_APP.task(bind=True, base=MonitoredTask) | ||||
|  | ||||
| @ -1,11 +1,16 @@ | ||||
| """policy API Views""" | ||||
| from django.core.cache import cache | ||||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| from rest_framework.mixins import ListModelMixin | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.serializers import ( | ||||
|     ModelSerializer, | ||||
|     PrimaryKeyRelatedField, | ||||
|     Serializer, | ||||
|     SerializerMethodField, | ||||
| ) | ||||
| from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet | ||||
| from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet | ||||
|  | ||||
| from authentik.policies.forms import GENERAL_FIELDS | ||||
| from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel | ||||
| @ -68,6 +73,10 @@ class PolicyViewSet(ReadOnlyModelViewSet): | ||||
|  | ||||
|     queryset = Policy.objects.all() | ||||
|     serializer_class = PolicySerializer | ||||
|     filterset_fields = { | ||||
|         "bindings": ["isnull"], | ||||
|         "promptstage": ["isnull"], | ||||
|     } | ||||
|  | ||||
|     def get_queryset(self): | ||||
|         return Policy.objects.select_subclasses() | ||||
| @ -98,3 +107,14 @@ class PolicyBindingViewSet(ModelViewSet): | ||||
|     serializer_class = PolicyBindingSerializer | ||||
|     filterset_fields = ["policy", "target", "enabled", "order", "timeout"] | ||||
|     search_fields = ["policy__name"] | ||||
|  | ||||
|  | ||||
| class PolicyCacheViewSet(ListModelMixin, GenericViewSet): | ||||
|     """Info about cached policies""" | ||||
|  | ||||
|     queryset = Policy.objects.none() | ||||
|     serializer_class = Serializer | ||||
|  | ||||
|     def list(self, request: Request) -> Response: | ||||
|         """Info about cached policies""" | ||||
|         return Response(data={"pagination": {"count": len(cache.keys("policy_*"))}}) | ||||
|  | ||||
| @ -18,6 +18,7 @@ from django.utils import dateformat, timezone | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from jwkest.jwk import Key, RSAKey, SYMKey, import_rsa_key | ||||
| from jwkest.jws import JWS | ||||
| from rest_framework.serializers import Serializer | ||||
|  | ||||
| from authentik.core.models import ExpiringModel, PropertyMapping, Provider, User | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| @ -263,6 +264,12 @@ class OAuth2Provider(Provider): | ||||
|         launch_url = urlparse(main_url) | ||||
|         return main_url.replace(launch_url.path, "") | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> Type[Serializer]: | ||||
|         from authentik.providers.oauth2.api import OAuth2ProviderSerializer | ||||
|  | ||||
|         return OAuth2ProviderSerializer | ||||
|  | ||||
|     @property | ||||
|     def form(self) -> Type[ModelForm]: | ||||
|         from authentik.providers.oauth2.forms import OAuth2ProviderForm | ||||
|  | ||||
| @ -8,6 +8,7 @@ from django.db import models | ||||
| from django.forms import ModelForm | ||||
| from django.http import HttpRequest | ||||
| from django.utils.translation import gettext as _ | ||||
| from rest_framework.serializers import Serializer | ||||
|  | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.lib.models import DomainlessURLValidator | ||||
| @ -108,6 +109,12 @@ class ProxyProvider(OutpostModel, OAuth2Provider): | ||||
|  | ||||
|         return ProxyProviderForm | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> Type[Serializer]: | ||||
|         from authentik.providers.proxy.api import ProxyProviderSerializer | ||||
|  | ||||
|         return ProxyProviderSerializer | ||||
|  | ||||
|     @property | ||||
|     def launch_url(self) -> Optional[str]: | ||||
|         """Use external_host as launch URL""" | ||||
|  | ||||
| @ -7,6 +7,7 @@ from django.forms import ModelForm | ||||
| from django.http import HttpRequest | ||||
| from django.shortcuts import reverse | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from rest_framework.serializers import Serializer | ||||
| from structlog import get_logger | ||||
|  | ||||
| from authentik.core.models import PropertyMapping, Provider | ||||
| @ -145,6 +146,12 @@ class SAMLProvider(Provider): | ||||
|         launch_url = urlparse(self.acs_url) | ||||
|         return self.acs_url.replace(launch_url.path, "") | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> Type[Serializer]: | ||||
|         from authentik.providers.saml.api import SAMLPropertyMappingSerializer | ||||
|  | ||||
|         return SAMLPropertyMappingSerializer | ||||
|  | ||||
|     @property | ||||
|     def form(self) -> Type[ModelForm]: | ||||
|         from authentik.providers.saml.forms import SAMLProviderForm | ||||
|  | ||||
| @ -19,7 +19,7 @@ services: | ||||
|     networks: | ||||
|       - internal | ||||
|   server: | ||||
|     image: beryju/authentik:${AUTHENTIK_TAG:-0.13.1-stable} | ||||
|     image: beryju/authentik:${AUTHENTIK_TAG:-0.13.2-stable} | ||||
|     command: server | ||||
|     environment: | ||||
|       AUTHENTIK_REDIS__HOST: redis | ||||
| @ -44,7 +44,7 @@ services: | ||||
|     env_file: | ||||
|       - .env | ||||
|   worker: | ||||
|     image: beryju/authentik:${AUTHENTIK_TAG:-0.13.1-stable} | ||||
|     image: beryju/authentik:${AUTHENTIK_TAG:-0.13.2-stable} | ||||
|     command: worker | ||||
|     networks: | ||||
|       - internal | ||||
| @ -60,7 +60,7 @@ services: | ||||
|     env_file: | ||||
|       - .env | ||||
|   static: | ||||
|     image: beryju/authentik-static:${AUTHENTIK_TAG:-0.13.1-stable} | ||||
|     image: beryju/authentik-static:${AUTHENTIK_TAG:-0.13.2-stable} | ||||
|     networks: | ||||
|       - internal | ||||
|     labels: | ||||
|  | ||||
| @ -4,7 +4,7 @@ name: authentik | ||||
| home: https://goauthentik.io | ||||
| sources: | ||||
|   - https://github.com/BeryJu/authentik | ||||
| version: "0.13.1-stable" | ||||
| version: "0.13.2-stable" | ||||
| icon: https://raw.githubusercontent.com/BeryJu/authentik/master/web/icons/icon.svg | ||||
| dependencies: | ||||
|   - name: postgresql | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| |-----------------------------------|-------------------------|-------------| | ||||
| | image.name                        | beryju/authentik         | Image used to run the authentik server and worker | | ||||
| | image.name_static                 | beryju/authentik-static  | Image used to run the authentik static server (CSS and JS Files) | | ||||
| | image.tag                         | 0.13.1-stable              | Image tag | | ||||
| | image.tag                         | 0.13.2-stable              | Image tag | | ||||
| | image.pullPolicy                  | IfNotPresent            | Image Pull Policy used for all deployments | | ||||
| | serverReplicas                    | 1                       | Replicas for the Server deployment | | ||||
| | workerReplicas                    | 1                       | Replicas for the Worker deployment | | ||||
|  | ||||
| @ -5,7 +5,7 @@ image: | ||||
|   name: beryju/authentik | ||||
|   name_static: beryju/authentik-static | ||||
|   name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended | ||||
|   tag: 0.13.1-stable | ||||
|   tag: 0.13.2-stable | ||||
|   pullPolicy: IfNotPresent | ||||
|  | ||||
| serverReplicas: 1 | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| package pkg | ||||
|  | ||||
| const VERSION = "0.13.1-stable" | ||||
| const VERSION = "0.13.2-stable" | ||||
|  | ||||
							
								
								
									
										341
									
								
								swagger.yaml
									
									
									
									
									
								
							
							
						
						
									
										341
									
								
								swagger.yaml
									
									
									
									
									
								
							| @ -22,11 +22,11 @@ paths: | ||||
|   /admin/metrics/: | ||||
|     get: | ||||
|       operationId: admin_metrics_list | ||||
|       description: Return single instance of AdministrationMetricsSerializer | ||||
|       description: Login Metrics per 1h | ||||
|       parameters: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Overview View | ||||
|           description: Login Metrics per 1h | ||||
|           schema: | ||||
|             description: '' | ||||
|             type: array | ||||
| @ -35,22 +35,6 @@ paths: | ||||
|       tags: | ||||
|         - admin | ||||
|     parameters: [] | ||||
|   /admin/overview/: | ||||
|     get: | ||||
|       operationId: admin_overview_list | ||||
|       description: Return single instance of AdministrationOverviewSerializer | ||||
|       parameters: [] | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Overview View | ||||
|           schema: | ||||
|             description: '' | ||||
|             type: array | ||||
|             items: | ||||
|               $ref: '#/definitions/AdministrationOverview' | ||||
|       tags: | ||||
|         - admin | ||||
|     parameters: [] | ||||
|   /admin/system_tasks/: | ||||
|     get: | ||||
|       operationId: admin_system_tasks_list | ||||
| @ -82,6 +66,95 @@ paths: | ||||
|         in: path | ||||
|         required: true | ||||
|         type: string | ||||
|   /admin/version/: | ||||
|     get: | ||||
|       operationId: admin_version_list | ||||
|       description: Get running and latest version. | ||||
|       parameters: | ||||
|         - name: ordering | ||||
|           in: query | ||||
|           description: Which field to use when ordering the results. | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: search | ||||
|           in: query | ||||
|           description: A search term. | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: page | ||||
|           in: query | ||||
|           description: A page number within the paginated result set. | ||||
|           required: false | ||||
|           type: integer | ||||
|         - name: page_size | ||||
|           in: query | ||||
|           description: Number of results to return per page. | ||||
|           required: false | ||||
|           type: integer | ||||
|       responses: | ||||
|         '200': | ||||
|           description: Get running and latest version. | ||||
|           schema: | ||||
|             description: '' | ||||
|             type: array | ||||
|             items: | ||||
|               $ref: '#/definitions/Version' | ||||
|       tags: | ||||
|         - admin | ||||
|     parameters: [] | ||||
|   /admin/workers/: | ||||
|     get: | ||||
|       operationId: admin_workers_list | ||||
|       description: Get currently connected worker count. | ||||
|       parameters: | ||||
|         - name: ordering | ||||
|           in: query | ||||
|           description: Which field to use when ordering the results. | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: search | ||||
|           in: query | ||||
|           description: A search term. | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: page | ||||
|           in: query | ||||
|           description: A page number within the paginated result set. | ||||
|           required: false | ||||
|           type: integer | ||||
|         - name: page_size | ||||
|           in: query | ||||
|           description: Number of results to return per page. | ||||
|           required: false | ||||
|           type: integer | ||||
|       responses: | ||||
|         '200': | ||||
|           description: '' | ||||
|           schema: | ||||
|             required: | ||||
|               - count | ||||
|               - results | ||||
|             type: object | ||||
|             properties: | ||||
|               count: | ||||
|                 type: integer | ||||
|               next: | ||||
|                 type: string | ||||
|                 format: uri | ||||
|                 x-nullable: true | ||||
|               previous: | ||||
|                 type: string | ||||
|                 format: uri | ||||
|                 x-nullable: true | ||||
|               results: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   description: '' | ||||
|                   type: object | ||||
|                   properties: {} | ||||
|       tags: | ||||
|         - admin | ||||
|     parameters: [] | ||||
|   /audit/events/: | ||||
|     get: | ||||
|       operationId: audit_events_list | ||||
| @ -1062,6 +1135,59 @@ paths: | ||||
|         required: true | ||||
|         type: string | ||||
|         format: uuid | ||||
|   /flows/cached/: | ||||
|     get: | ||||
|       operationId: flows_cached_list | ||||
|       description: Info about cached flows | ||||
|       parameters: | ||||
|         - name: ordering | ||||
|           in: query | ||||
|           description: Which field to use when ordering the results. | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: search | ||||
|           in: query | ||||
|           description: A search term. | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: page | ||||
|           in: query | ||||
|           description: A page number within the paginated result set. | ||||
|           required: false | ||||
|           type: integer | ||||
|         - name: page_size | ||||
|           in: query | ||||
|           description: Number of results to return per page. | ||||
|           required: false | ||||
|           type: integer | ||||
|       responses: | ||||
|         '200': | ||||
|           description: '' | ||||
|           schema: | ||||
|             required: | ||||
|               - count | ||||
|               - results | ||||
|             type: object | ||||
|             properties: | ||||
|               count: | ||||
|                 type: integer | ||||
|               next: | ||||
|                 type: string | ||||
|                 format: uri | ||||
|                 x-nullable: true | ||||
|               previous: | ||||
|                 type: string | ||||
|                 format: uri | ||||
|                 x-nullable: true | ||||
|               results: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   description: '' | ||||
|                   type: object | ||||
|                   properties: {} | ||||
|       tags: | ||||
|         - flows | ||||
|     parameters: [] | ||||
|   /flows/instances/: | ||||
|     get: | ||||
|       operationId: flows_instances_list | ||||
| @ -1702,6 +1828,16 @@ paths: | ||||
|       operationId: policies_all_list | ||||
|       description: Policy Viewset | ||||
|       parameters: | ||||
|         - name: bindings__isnull | ||||
|           in: query | ||||
|           description: '' | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: promptstage__isnull | ||||
|           in: query | ||||
|           description: '' | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: ordering | ||||
|           in: query | ||||
|           description: Which field to use when ordering the results. | ||||
| @ -1919,6 +2055,59 @@ paths: | ||||
|         required: true | ||||
|         type: string | ||||
|         format: uuid | ||||
|   /policies/cached/: | ||||
|     get: | ||||
|       operationId: policies_cached_list | ||||
|       description: Info about cached policies | ||||
|       parameters: | ||||
|         - name: ordering | ||||
|           in: query | ||||
|           description: Which field to use when ordering the results. | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: search | ||||
|           in: query | ||||
|           description: A search term. | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: page | ||||
|           in: query | ||||
|           description: A page number within the paginated result set. | ||||
|           required: false | ||||
|           type: integer | ||||
|         - name: page_size | ||||
|           in: query | ||||
|           description: Number of results to return per page. | ||||
|           required: false | ||||
|           type: integer | ||||
|       responses: | ||||
|         '200': | ||||
|           description: '' | ||||
|           schema: | ||||
|             required: | ||||
|               - count | ||||
|               - results | ||||
|             type: object | ||||
|             properties: | ||||
|               count: | ||||
|                 type: integer | ||||
|               next: | ||||
|                 type: string | ||||
|                 format: uri | ||||
|                 x-nullable: true | ||||
|               previous: | ||||
|                 type: string | ||||
|                 format: uri | ||||
|                 x-nullable: true | ||||
|               results: | ||||
|                 type: array | ||||
|                 items: | ||||
|                   description: '' | ||||
|                   type: object | ||||
|                   properties: {} | ||||
|       tags: | ||||
|         - policies | ||||
|     parameters: [] | ||||
|   /policies/dummy/: | ||||
|     get: | ||||
|       operationId: policies_dummy_list | ||||
| @ -3264,6 +3453,11 @@ paths: | ||||
|       operationId: providers_all_list | ||||
|       description: Provider Viewset | ||||
|       parameters: | ||||
|         - name: application__isnull | ||||
|           in: query | ||||
|           description: '' | ||||
|           required: false | ||||
|           type: string | ||||
|         - name: ordering | ||||
|           in: query | ||||
|           description: Which field to use when ordering the results. | ||||
| @ -3309,6 +3503,22 @@ paths: | ||||
|                   $ref: '#/definitions/Provider' | ||||
|       tags: | ||||
|         - providers | ||||
|     post: | ||||
|       operationId: providers_all_create | ||||
|       description: Provider Viewset | ||||
|       parameters: | ||||
|         - name: data | ||||
|           in: body | ||||
|           required: true | ||||
|           schema: | ||||
|             $ref: '#/definitions/Provider' | ||||
|       responses: | ||||
|         '201': | ||||
|           description: '' | ||||
|           schema: | ||||
|             $ref: '#/definitions/Provider' | ||||
|       tags: | ||||
|         - providers | ||||
|     parameters: [] | ||||
|   /providers/all/{id}/: | ||||
|     get: | ||||
| @ -3322,6 +3532,47 @@ paths: | ||||
|             $ref: '#/definitions/Provider' | ||||
|       tags: | ||||
|         - providers | ||||
|     put: | ||||
|       operationId: providers_all_update | ||||
|       description: Provider Viewset | ||||
|       parameters: | ||||
|         - name: data | ||||
|           in: body | ||||
|           required: true | ||||
|           schema: | ||||
|             $ref: '#/definitions/Provider' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: '' | ||||
|           schema: | ||||
|             $ref: '#/definitions/Provider' | ||||
|       tags: | ||||
|         - providers | ||||
|     patch: | ||||
|       operationId: providers_all_partial_update | ||||
|       description: Provider Viewset | ||||
|       parameters: | ||||
|         - name: data | ||||
|           in: body | ||||
|           required: true | ||||
|           schema: | ||||
|             $ref: '#/definitions/Provider' | ||||
|       responses: | ||||
|         '200': | ||||
|           description: '' | ||||
|           schema: | ||||
|             $ref: '#/definitions/Provider' | ||||
|       tags: | ||||
|         - providers | ||||
|     delete: | ||||
|       operationId: providers_all_delete | ||||
|       description: Provider Viewset | ||||
|       parameters: [] | ||||
|       responses: | ||||
|         '204': | ||||
|           description: '' | ||||
|       tags: | ||||
|         - providers | ||||
|     parameters: | ||||
|       - name: id | ||||
|         in: path | ||||
| @ -6421,7 +6672,7 @@ paths: | ||||
|         format: uuid | ||||
| definitions: | ||||
|   AdministrationMetrics: | ||||
|     description: Overview View | ||||
|     description: Login Metrics per 1h | ||||
|     type: object | ||||
|     properties: | ||||
|       logins_per_1h: | ||||
| @ -6432,38 +6683,6 @@ definitions: | ||||
|         title: Logins failed per 1h | ||||
|         type: string | ||||
|         readOnly: true | ||||
|   AdministrationOverview: | ||||
|     description: Overview View | ||||
|     type: object | ||||
|     properties: | ||||
|       version: | ||||
|         title: Version | ||||
|         type: string | ||||
|         readOnly: true | ||||
|       version_latest: | ||||
|         title: Version latest | ||||
|         type: string | ||||
|         readOnly: true | ||||
|       worker_count: | ||||
|         title: Worker count | ||||
|         type: integer | ||||
|         readOnly: true | ||||
|       providers_without_application: | ||||
|         title: Providers without application | ||||
|         type: integer | ||||
|         readOnly: true | ||||
|       policies_without_binding: | ||||
|         title: Policies without binding | ||||
|         type: integer | ||||
|         readOnly: true | ||||
|       cached_policies: | ||||
|         title: Cached policies | ||||
|         type: integer | ||||
|         readOnly: true | ||||
|       cached_flows: | ||||
|         title: Cached flows | ||||
|         type: integer | ||||
|         readOnly: true | ||||
|   Task: | ||||
|     description: Serialize TaskInfo and TaskResult | ||||
|     required: | ||||
| @ -6494,6 +6713,22 @@ definitions: | ||||
|         type: array | ||||
|         items: | ||||
|           type: string | ||||
|   Version: | ||||
|     description: Get running and latest version. | ||||
|     type: object | ||||
|     properties: | ||||
|       version_current: | ||||
|         title: Version current | ||||
|         type: string | ||||
|         readOnly: true | ||||
|       version_latest: | ||||
|         title: Version latest | ||||
|         type: string | ||||
|         readOnly: true | ||||
|       outdated: | ||||
|         title: Outdated | ||||
|         type: boolean | ||||
|         readOnly: true | ||||
|   Event: | ||||
|     description: Event Serializer | ||||
|     required: | ||||
| @ -7480,6 +7715,7 @@ definitions: | ||||
|     description: Provider Serializer | ||||
|     required: | ||||
|       - name | ||||
|       - application | ||||
|       - authorization_flow | ||||
|     type: object | ||||
|     properties: | ||||
| @ -7491,6 +7727,9 @@ definitions: | ||||
|         title: Name | ||||
|         type: string | ||||
|         minLength: 1 | ||||
|       application: | ||||
|         title: Application | ||||
|         type: string | ||||
|       authorization_flow: | ||||
|         title: Authorization flow | ||||
|         description: Flow used when authorizing this provider. | ||||
|  | ||||
							
								
								
									
										108
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										108
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -142,13 +142,13 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/browser": { | ||||
|             "version": "5.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.0.tgz", | ||||
|             "integrity": "sha512-kRlt1mE2wrYjspnIupNnPxqsUrRuy02SuXhbpP7J6uu8QasoEmJ78hk0hHz4jOZRmuWwfs2zIXD4tLGgWOKq8A==", | ||||
|             "version": "5.29.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.1.tgz", | ||||
|             "integrity": "sha512-cVlXoQBJ64eNNkQuOB+bS6sK5KWV+Fw+ZYxT+XqjpeXkOPaxh8aeoi9CHz2DsFfbLV91P4AnXZEUdDl+7ktQNg==", | ||||
|             "requires": { | ||||
|                 "@sentry/core": "5.29.0", | ||||
|                 "@sentry/types": "5.29.0", | ||||
|                 "@sentry/utils": "5.29.0", | ||||
|                 "@sentry/core": "5.29.1", | ||||
|                 "@sentry/types": "5.29.1", | ||||
|                 "@sentry/utils": "5.29.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -160,14 +160,14 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/core": { | ||||
|             "version": "5.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.0.tgz", | ||||
|             "integrity": "sha512-a1sZBJ2u3NG0YDlGvOTwUCWiNjhfmDtAQiKK1o6RIIbcrWy9TlSps7CYDkBP239Y3A4pnvohjEEKEP3v3L3LZQ==", | ||||
|             "version": "5.29.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.1.tgz", | ||||
|             "integrity": "sha512-SMybIx9IlswkJ7a61ez/zjdiMdAo51Adpo4nVrzke2k84U/t726/EbJj0FJ4vVgsGdLCvSSZ6v7BQlINcwWupg==", | ||||
|             "requires": { | ||||
|                 "@sentry/hub": "5.29.0", | ||||
|                 "@sentry/minimal": "5.29.0", | ||||
|                 "@sentry/types": "5.29.0", | ||||
|                 "@sentry/utils": "5.29.0", | ||||
|                 "@sentry/hub": "5.29.1", | ||||
|                 "@sentry/minimal": "5.29.1", | ||||
|                 "@sentry/types": "5.29.1", | ||||
|                 "@sentry/utils": "5.29.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -179,12 +179,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/hub": { | ||||
|             "version": "5.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.0.tgz", | ||||
|             "integrity": "sha512-kcDPQsRG4cFdmqDh+TzjeO7lWYxU8s1dZYAbbl1J4uGKmhNB0J7I4ak4SGwTsXLY6fhbierxr6PRaoNojCxjPw==", | ||||
|             "version": "5.29.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.1.tgz", | ||||
|             "integrity": "sha512-Ig/vqCiJcsnGaWajkWRFH+5IKeo50ZtsjM0zJb8IfTadLjQuF/gTQst0aXO3l6q4HzveeGsELY8jlm6WVcq9Aw==", | ||||
|             "requires": { | ||||
|                 "@sentry/types": "5.29.0", | ||||
|                 "@sentry/utils": "5.29.0", | ||||
|                 "@sentry/types": "5.29.1", | ||||
|                 "@sentry/utils": "5.29.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -196,12 +196,12 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/minimal": { | ||||
|             "version": "5.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.0.tgz", | ||||
|             "integrity": "sha512-nhXofdjtO41/caiF1wk1oT3p/QuhOZDYdF/b29DoD2MiAMK9IjhhOXI/gqaRpDKkXlDvd95fDTcx4t/MqqcKXA==", | ||||
|             "version": "5.29.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.1.tgz", | ||||
|             "integrity": "sha512-lAa3+Duxum1qQvR0tKiBUsH6Ehit3g/vO53SqBib7YK3qdvIUWHacmkJvfz/AeSvVnpJ9bsBMCVRJNSVe8BPVA==", | ||||
|             "requires": { | ||||
|                 "@sentry/hub": "5.29.0", | ||||
|                 "@sentry/types": "5.29.0", | ||||
|                 "@sentry/hub": "5.29.1", | ||||
|                 "@sentry/types": "5.29.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
| @ -213,51 +213,17 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/tracing": { | ||||
|             "version": "5.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.29.0.tgz", | ||||
|             "integrity": "sha512-2ZITUH7Eur7IkmRAd5gw8Xt2Sfc28btCnT7o2P2J8ZPD65e99ATqjxXPokx0+6zEkTsstIDD3mbyuwkpbuvuTA==", | ||||
|             "version": "5.29.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.29.1.tgz", | ||||
|             "integrity": "sha512-iWfPtDhf5X7N9R5WB3vX/wlyFVsGG8iMx4hLIP+6bj8EcPYnZfeP6Sxn65a0ACT/FKv7SMBoZ1qPDzmvk0bviw==", | ||||
|             "requires": { | ||||
|                 "@sentry/hub": "5.29.0", | ||||
|                 "@sentry/minimal": "5.29.0", | ||||
|                 "@sentry/types": "5.29.0", | ||||
|                 "@sentry/utils": "5.29.0", | ||||
|                 "@sentry/hub": "5.29.1", | ||||
|                 "@sentry/minimal": "5.29.1", | ||||
|                 "@sentry/types": "5.29.1", | ||||
|                 "@sentry/utils": "5.29.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
|                 "@sentry/hub": { | ||||
|                     "version": "5.29.0", | ||||
|                     "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.0.tgz", | ||||
|                     "integrity": "sha512-kcDPQsRG4cFdmqDh+TzjeO7lWYxU8s1dZYAbbl1J4uGKmhNB0J7I4ak4SGwTsXLY6fhbierxr6PRaoNojCxjPw==", | ||||
|                     "requires": { | ||||
|                         "@sentry/types": "5.29.0", | ||||
|                         "@sentry/utils": "5.29.0", | ||||
|                         "tslib": "^1.9.3" | ||||
|                     } | ||||
|                 }, | ||||
|                 "@sentry/minimal": { | ||||
|                     "version": "5.29.0", | ||||
|                     "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.0.tgz", | ||||
|                     "integrity": "sha512-nhXofdjtO41/caiF1wk1oT3p/QuhOZDYdF/b29DoD2MiAMK9IjhhOXI/gqaRpDKkXlDvd95fDTcx4t/MqqcKXA==", | ||||
|                     "requires": { | ||||
|                         "@sentry/hub": "5.29.0", | ||||
|                         "@sentry/types": "5.29.0", | ||||
|                         "tslib": "^1.9.3" | ||||
|                     } | ||||
|                 }, | ||||
|                 "@sentry/types": { | ||||
|                     "version": "5.29.0", | ||||
|                     "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.0.tgz", | ||||
|                     "integrity": "sha512-iDkxT/9sT3UF+Xb+JyLjZ5caMXsgLfRyV9VXQEiR2J6mgpMielj184d9jeF3bm/VMuAf/VFFqrHlcVsVgmrrMw==" | ||||
|                 }, | ||||
|                 "@sentry/utils": { | ||||
|                     "version": "5.29.0", | ||||
|                     "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.0.tgz", | ||||
|                     "integrity": "sha512-b2B1gshw2u3EHlAi84PuI5sfmLKXW1z9enMMhNuuNT/CoRp+g5kMAcUv/qYTws7UNnYSvTuVGuZG30v1e0hP9A==", | ||||
|                     "requires": { | ||||
|                         "@sentry/types": "5.29.0", | ||||
|                         "tslib": "^1.9.3" | ||||
|                     } | ||||
|                 }, | ||||
|                 "tslib": { | ||||
|                     "version": "1.14.1", | ||||
|                     "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", | ||||
| @ -266,16 +232,16 @@ | ||||
|             } | ||||
|         }, | ||||
|         "@sentry/types": { | ||||
|             "version": "5.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.0.tgz", | ||||
|             "integrity": "sha512-iDkxT/9sT3UF+Xb+JyLjZ5caMXsgLfRyV9VXQEiR2J6mgpMielj184d9jeF3bm/VMuAf/VFFqrHlcVsVgmrrMw==" | ||||
|             "version": "5.29.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.1.tgz", | ||||
|             "integrity": "sha512-QXZBA1gJheMYTGFV+UUhr3+jKpGZqPx8kEJABs8htlKabCDJlEeoFNmeqPuVxCxukoy5ZaaHACoE+2Z87T0g2A==" | ||||
|         }, | ||||
|         "@sentry/utils": { | ||||
|             "version": "5.29.0", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.0.tgz", | ||||
|             "integrity": "sha512-b2B1gshw2u3EHlAi84PuI5sfmLKXW1z9enMMhNuuNT/CoRp+g5kMAcUv/qYTws7UNnYSvTuVGuZG30v1e0hP9A==", | ||||
|             "version": "5.29.1", | ||||
|             "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.1.tgz", | ||||
|             "integrity": "sha512-FOhWxASvIQREAlSuWf3Vmb4uIkG0fmRdHkULpuv5dFmrMX2PpudYAppQtS8K9V4BYxFy6KFdUht1Qz5zYTecMw==", | ||||
|             "requires": { | ||||
|                 "@sentry/types": "5.29.0", | ||||
|                 "@sentry/types": "5.29.1", | ||||
|                 "tslib": "^1.9.3" | ||||
|             }, | ||||
|             "dependencies": { | ||||
|  | ||||
| @ -9,8 +9,8 @@ | ||||
|     "dependencies": { | ||||
|         "@fortawesome/fontawesome-free": "^5.15.1", | ||||
|         "@patternfly/patternfly": "^4.70.2", | ||||
|         "@sentry/browser": "^5.29.0", | ||||
|         "@sentry/tracing": "^5.29.0", | ||||
|         "@sentry/browser": "^5.29.1", | ||||
|         "@sentry/tracing": "^5.29.1", | ||||
|         "@types/chart.js": "^2.9.29", | ||||
|         "@types/codemirror": "0.0.102", | ||||
|         "chart.js": "^2.9.4", | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./client"; | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./Client"; | ||||
| 
 | ||||
| export class Application { | ||||
|     pk: string; | ||||
| @ -1,4 +1,4 @@ | ||||
| import { NotFoundError, RequestError } from "./errors"; | ||||
| import { NotFoundError, RequestError } from "./Error"; | ||||
| 
 | ||||
| export const VERSION = "v2beta"; | ||||
| 
 | ||||
| @ -1,4 +1,4 @@ | ||||
| import { DefaultClient } from "./client"; | ||||
| import { DefaultClient } from "./Client"; | ||||
| import * as Sentry from "@sentry/browser"; | ||||
| import { Integrations } from "@sentry/tracing"; | ||||
| import { VERSION } from "../constants"; | ||||
| @ -1,4 +1,4 @@ | ||||
| import { DefaultClient } from "./client"; | ||||
| import { DefaultClient } from "./Client"; | ||||
| 
 | ||||
| export class AuditEvent { | ||||
|     //audit/events/top_per_user/?filter_action=authorize_application
 | ||||
| @ -1,4 +1,4 @@ | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./client"; | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./Client"; | ||||
| 
 | ||||
| export enum FlowDesignation { | ||||
|     Authentication = "authentication", | ||||
| @ -33,6 +33,12 @@ export class Flow { | ||||
|     static list(filter?: QueryArguments): Promise<PBResponse<Flow>> { | ||||
|         return DefaultClient.fetch<PBResponse<Flow>>(["flows", "instances"], filter); | ||||
|     } | ||||
| 
 | ||||
|     static cached(): Promise<number> { | ||||
|         return DefaultClient.fetch<PBResponse<Flow>>(["flows", "cached"]).then(r => { | ||||
|             return r.pagination.count; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export class Stage { | ||||
							
								
								
									
										24
									
								
								web/src/api/Policies.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								web/src/api/Policies.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./Client"; | ||||
|  | ||||
| export class Policy { | ||||
|     pk: string; | ||||
|     name: string; | ||||
|  | ||||
|     constructor() { | ||||
|         throw Error(); | ||||
|     } | ||||
|  | ||||
|     static get(pk: string): Promise<Policy> { | ||||
|         return DefaultClient.fetch<Policy>(["policies", "all", pk]); | ||||
|     } | ||||
|  | ||||
|     static list(filter?: QueryArguments): Promise<PBResponse<Policy>> { | ||||
|         return DefaultClient.fetch<PBResponse<Policy>>(["policies", "all"], filter); | ||||
|     } | ||||
|  | ||||
|     static cached(): Promise<number> { | ||||
|         return DefaultClient.fetch<PBResponse<Policy>>(["policies", "cached"]).then(r => { | ||||
|             return r.pagination.count; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -1,10 +1,5 @@ | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./client"; | ||||
| 
 | ||||
| export interface Policy { | ||||
|     pk: string; | ||||
|     name: string; | ||||
|     [key: string]: unknown; | ||||
| } | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./Client"; | ||||
| import { Policy } from "./Policies"; | ||||
| 
 | ||||
| export class PolicyBinding { | ||||
|     pk: string; | ||||
							
								
								
									
										19
									
								
								web/src/api/Providers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								web/src/api/Providers.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./Client"; | ||||
|  | ||||
| export class Provider { | ||||
|     pk: number; | ||||
|     name: string; | ||||
|     authorization_flow: string; | ||||
|  | ||||
|     constructor() { | ||||
|         throw Error(); | ||||
|     } | ||||
|  | ||||
|     static get(slug: string): Promise<Provider> { | ||||
|         return DefaultClient.fetch<Provider>(["providers", "all", slug]); | ||||
|     } | ||||
|  | ||||
|     static list(filter?: QueryArguments): Promise<PBResponse<Provider>> { | ||||
|         return DefaultClient.fetch<PBResponse<Provider>>(["providers", "all"], filter); | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./client"; | ||||
| import { DefaultClient, PBResponse, QueryArguments } from "./Client"; | ||||
| 
 | ||||
| export class Source { | ||||
|     pk: string; | ||||
| @ -1,4 +1,4 @@ | ||||
| import { DefaultClient } from "./client"; | ||||
| import { DefaultClient } from "./Client"; | ||||
| 
 | ||||
| interface TokenResponse { | ||||
|     key: string; | ||||
| @ -1,4 +1,4 @@ | ||||
| import { DefaultClient, PBResponse } from "./client"; | ||||
| import { DefaultClient, PBResponse } from "./Client"; | ||||
| 
 | ||||
| let _globalMePromise: Promise<User>; | ||||
| 
 | ||||
							
								
								
									
										17
									
								
								web/src/api/Versions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								web/src/api/Versions.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| import { DefaultClient } from "./Client"; | ||||
|  | ||||
| export class Version { | ||||
|  | ||||
|     version_current: string; | ||||
|     version_latest: string; | ||||
|     outdated: boolean; | ||||
|  | ||||
|     constructor() { | ||||
|         throw Error(); | ||||
|     } | ||||
|  | ||||
|     static get(): Promise<Version> { | ||||
|         return DefaultClient.fetch<Version>(["admin", "version"]); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -1,20 +0,0 @@ | ||||
| import { DefaultClient } from "./client"; | ||||
|  | ||||
| export class AdminOverview { | ||||
|     version: string; | ||||
|     version_latest: string; | ||||
|     worker_count: number; | ||||
|     providers_without_application: number; | ||||
|     policies_without_binding: number; | ||||
|     cached_policies: number; | ||||
|     cached_flows: number; | ||||
|  | ||||
|     constructor() { | ||||
|         throw Error(); | ||||
|     } | ||||
|  | ||||
|     static get(): Promise<AdminOverview> { | ||||
|         return DefaultClient.fetch<AdminOverview>(["admin", "overview"]); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -162,10 +162,12 @@ select[multiple] { | ||||
|         background-color: var(--ak-dark-background-light); | ||||
|         color: var(--ak-dark-foreground); | ||||
|     } | ||||
|     .pf-m-tertiary, | ||||
|     .pf-c-button.pf-m-tertiary { | ||||
|         --pf-c-button--after--BorderColor: var(--ak-dark-foreground-darker); | ||||
|         color: var(--ak-dark-foreground-darker); | ||||
|     } | ||||
|     .pf-m-tertiary:hover, | ||||
|     .pf-c-button.pf-m-tertiary:hover { | ||||
|         --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter); | ||||
|     } | ||||
| @ -197,6 +199,9 @@ select[multiple] { | ||||
|     .pf-c-login__main { | ||||
|         background-color: var(--ak-dark-background); | ||||
|     } | ||||
|     .pf-c-login__main-body { | ||||
|         color: var(--ak-dark-foreground); | ||||
|     } | ||||
|     .pf-c-login__main-footer-links-item-link > img { | ||||
|         filter: invert(1); | ||||
|     } | ||||
|  | ||||
| @ -28,4 +28,4 @@ export const ColorStyles = css` | ||||
|         background-color: var(--pf-global--danger-color--100); | ||||
|     } | ||||
| `; | ||||
| export const VERSION = "0.13.1-stable"; | ||||
| export const VERSION = "0.13.2-stable"; | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu | ||||
| import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css"; | ||||
| // @ts-ignore | ||||
| import ButtonStyle from "@patternfly/patternfly/components/Button/button.css"; | ||||
| import { tokenByIdentifier } from "../../api/token"; | ||||
| import { tokenByIdentifier } from "../../api/Tokens"; | ||||
| import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants"; | ||||
|  | ||||
| @customElement("ak-token-copy-button") | ||||
|  | ||||
| @ -2,6 +2,7 @@ import { gettext } from "django"; | ||||
| import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { ifDefined } from "lit-html/directives/if-defined"; | ||||
| import { COMMON_STYLES } from "../../common/styles"; | ||||
| import { ColorStyles } from "../../constants"; | ||||
|  | ||||
| @customElement("ak-aggregate-card") | ||||
| export class AggregateCard extends LitElement { | ||||
| @ -24,22 +25,26 @@ export class AggregateCard extends LitElement { | ||||
|                 text-align: center; | ||||
|                 color: var(--pf-global--Color--100); | ||||
|             } | ||||
|         `]); | ||||
|         `, ColorStyles]); | ||||
|     } | ||||
|  | ||||
|     renderInner(): TemplateResult { | ||||
|         return html`<slot></slot>`; | ||||
|     } | ||||
|  | ||||
|     renderHeaderLink(): TemplateResult { | ||||
|         return html`${this.headerLink ? html`<a href="${this.headerLink}"> | ||||
|             <i class="fa fa-external-link-alt"> </i> | ||||
|         </a>` : ""}`; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<div class="pf-c-card pf-c-card-aggregate"> | ||||
|             <div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between"> | ||||
|                 <div class="pf-c-card__header-main"> | ||||
|                     <i class="${ifDefined(this.icon)}"></i> ${this.header ? gettext(this.header) : ""} | ||||
|                 </div> | ||||
|                 ${this.headerLink ? html`<a href="${this.headerLink}"> | ||||
|                     <i class="fa fa-external-link-alt"> </i> | ||||
|                 </a>` : ""} | ||||
|                 ${this.renderHeaderLink()} | ||||
|             </div> | ||||
|             <div class="pf-c-card__body center-value"> | ||||
|                 ${this.renderInner()} | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { gettext } from "django"; | ||||
| import { LitElement, html, customElement, TemplateResult, property } from "lit-element"; | ||||
| import { DefaultClient } from "../../api/client"; | ||||
| import { DefaultClient } from "../../api/Client"; | ||||
| import "./Message"; | ||||
| import { APIMessage } from "./Message"; | ||||
|  | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement, html, property, TemplateResult } from "lit-element"; | ||||
| import { PBResponse } from "../../api/client"; | ||||
| import { PolicyBinding } from "../../api/policy_binding"; | ||||
| import { PBResponse } from "../../api/Client"; | ||||
| import { Table } from "../../elements/table/Table"; | ||||
| import { PolicyBinding } from "../../api/PolicyBindings"; | ||||
|  | ||||
| import "../../elements/Tabs"; | ||||
| import "../../elements/AdminLoginsChart"; | ||||
|  | ||||
| @ -65,6 +65,32 @@ export class SidebarItem { | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     async render(activePath: string): Promise<TemplateResult> { | ||||
|         if (this.condition) { | ||||
|             const result = await this.condition(); | ||||
|             if (!result) { | ||||
|                 return html``; | ||||
|             } | ||||
|         } | ||||
|         return html` <li class="pf-c-nav__item ${this.hasChildren() ? "pf-m-expandable pf-m-expanded" : ""}"> | ||||
|             ${this.path ? | ||||
|         html`<a href="#${this.path}" class="pf-c-nav__link ${this.isActive(activePath) ? "pf-m-current" : ""}"> | ||||
|                         ${this.name} | ||||
|                     </a>` : | ||||
|         html`<a class="pf-c-nav__link" aria-expanded="true"> | ||||
|                         ${this.name} | ||||
|                         <span class="pf-c-nav__toggle"> | ||||
|                             <i class="fas fa-angle-right" aria-hidden="true"></i> | ||||
|                         </span> | ||||
|                     </a> | ||||
|                     <section class="pf-c-nav__subnav"> | ||||
|                         <ul class="pf-c-nav__simple-list"> | ||||
|                             ${this._children.map((i) => until(i.render(activePath), html``))} | ||||
|                         </ul> | ||||
|                     </section>`} | ||||
|         </li>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @customElement("ak-sidebar") | ||||
| @ -118,37 +144,11 @@ export class Sidebar extends LitElement { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     async renderItem(item: SidebarItem): Promise<TemplateResult> { | ||||
|         if (item.condition) { | ||||
|             const result = await item.condition(); | ||||
|             if (!result) { | ||||
|                 return html``; | ||||
|             } | ||||
|         } | ||||
|         return html` <li class="pf-c-nav__item ${item.hasChildren() ? "pf-m-expandable pf-m-expanded" : ""}"> | ||||
|             ${item.path ? | ||||
|         html`<a href="#${item.path}" class="pf-c-nav__link ${item.isActive(this.activePath) ? "pf-m-current": ""}"> | ||||
|                         ${item.name} | ||||
|                     </a>` : | ||||
|         html`<a class="pf-c-nav__link" aria-expanded="true"> | ||||
|                         ${item.name} | ||||
|                         <span class="pf-c-nav__toggle"> | ||||
|                             <i class="fas fa-angle-right" aria-hidden="true"></i> | ||||
|                         </span> | ||||
|                     </a> | ||||
|                     <section class="pf-c-nav__subnav"> | ||||
|                         <ul class="pf-c-nav__simple-list"> | ||||
|                             ${item._children.map((i) => until(this.renderItem(i), html``))} | ||||
|                         </ul> | ||||
|                     </section>`} | ||||
|         </li>`; | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<nav class="pf-c-nav" aria-label="Global"> | ||||
|             <ak-sidebar-brand></ak-sidebar-brand> | ||||
|             <ul class="pf-c-nav__list"> | ||||
|                 ${this.items.map((i) => until(this.renderItem(i), html``))} | ||||
|                 ${this.items.map((i) => until(i.render(this.activePath), html``))} | ||||
|             </ul> | ||||
|             <ak-sidebar-user></ak-sidebar-user> | ||||
|         </nav>`; | ||||
|  | ||||
| @ -3,7 +3,7 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu | ||||
| import PageStyle from "@patternfly/patternfly/components/Page/page.css"; | ||||
| // @ts-ignore | ||||
| import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css"; | ||||
| import { Config } from "../../api/config"; | ||||
| import { Config } from "../../api/Config"; | ||||
|  | ||||
| export const DefaultConfig: Config = { | ||||
|     branding_logo: " /static/dist/assets/icons/icon_left_brand.svg", | ||||
|  | ||||
| @ -5,7 +5,7 @@ import NavStyle from "@patternfly/patternfly/components/Nav/nav.css"; | ||||
| import fa from "@fortawesome/fontawesome-free/css/all.css"; | ||||
| // @ts-ignore | ||||
| import AvatarStyle from "@patternfly/patternfly/components/Avatar/avatar.css"; | ||||
| import { User } from "../../api/user"; | ||||
| import { User } from "../../api/Users"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
|  | ||||
| @customElement("ak-sidebar-user") | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { gettext } from "django"; | ||||
| import { CSSResult, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { PBResponse } from "../../api/client"; | ||||
| import { PBResponse } from "../../api/Client"; | ||||
| import { COMMON_STYLES } from "../../common/styles"; | ||||
|  | ||||
| import "./TablePagination"; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { COMMON_STYLES } from "../../common/styles"; | ||||
| import { PBPagination } from "../../api/client"; | ||||
| import { PBPagination } from "../../api/Client"; | ||||
|  | ||||
| @customElement("ak-table-pagination") | ||||
| export class TablePagination extends LitElement { | ||||
|  | ||||
| @ -1,17 +1,19 @@ | ||||
| import { customElement } from "lit-element"; | ||||
| import { User } from "../api/user"; | ||||
| import { User } from "../api/Users"; | ||||
| import { SidebarItem } from "../elements/sidebar/Sidebar"; | ||||
| import { SLUG_REGEX } from "../elements/router/Route"; | ||||
| import { Interface } from "./Interface"; | ||||
|  | ||||
| export const SIDEBAR_ITEMS: SidebarItem[] = [ | ||||
|     new SidebarItem("Library", "/library/"), | ||||
|     new SidebarItem("Monitor", "/audit/audit").when((): Promise<boolean> => { | ||||
|     new SidebarItem("Monitor").children( | ||||
|         new SidebarItem("Overview", "/administration/overview/"), | ||||
|         new SidebarItem("System Tasks", "/administration/tasks/"), | ||||
|         new SidebarItem("Events", "/audit/audit"), | ||||
|     ).when((): Promise<boolean> => { | ||||
|         return User.me().then(u => u.is_superuser); | ||||
|     }), | ||||
|     new SidebarItem("Administration").children( | ||||
|         new SidebarItem("Overview", "/administration/overview-ng/"), | ||||
|         new SidebarItem("System Tasks", "/administration/tasks/"), | ||||
|         new SidebarItem("Applications", "/administration/applications/").activeWhen( | ||||
|             `^/applications/(?<slug>${SLUG_REGEX})/$` | ||||
|         ), | ||||
| @ -19,27 +21,29 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ | ||||
|             `^/sources/(?<slug>${SLUG_REGEX})/$`, | ||||
|         ), | ||||
|         new SidebarItem("Providers", "/administration/providers/"), | ||||
|         new SidebarItem("Flows").children( | ||||
|             new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})/$`), | ||||
|             new SidebarItem("Stages", "/administration/stages/"), | ||||
|             new SidebarItem("Prompts", "/administration/stages/prompts/"), | ||||
|             new SidebarItem("Invitations", "/administration/stages/invitations/"), | ||||
|         ), | ||||
|         new SidebarItem("User Management").children( | ||||
|             new SidebarItem("User", "/administration/users/"), | ||||
|             new SidebarItem("Groups", "/administration/groups/") | ||||
|         ), | ||||
|         new SidebarItem("Outposts").children( | ||||
|             new SidebarItem("Outposts", "/administration/outposts/"), | ||||
|             new SidebarItem("Service Connections", "/administration/outposts/service_connections/") | ||||
|         ), | ||||
|         new SidebarItem("Outposts", "/administration/outposts/"), | ||||
|         new SidebarItem("Outpost Service Connections", "/administration/outposts/service_connections/"), | ||||
|         new SidebarItem("Policies", "/administration/policies/"), | ||||
|         new SidebarItem("Property Mappings", "/administration/property-mappings"), | ||||
|         new SidebarItem("Certificates", "/administration/crypto/certificates"), | ||||
|         new SidebarItem("Tokens", "/administration/tokens/"), | ||||
|     ).when((): Promise<boolean> => { | ||||
|         return User.me().then(u => u.is_superuser); | ||||
|     }) | ||||
|     }), | ||||
|     new SidebarItem("Flows").children( | ||||
|         new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?<slug>${SLUG_REGEX})/$`), | ||||
|         new SidebarItem("Stages", "/administration/stages/"), | ||||
|         new SidebarItem("Prompts", "/administration/stages/prompts/"), | ||||
|         new SidebarItem("Invitations", "/administration/stages/invitations/"), | ||||
|     ).when((): Promise<boolean> => { | ||||
|         return User.me().then(u => u.is_superuser); | ||||
|     }), | ||||
|     new SidebarItem("User Management").children( | ||||
|         new SidebarItem("User", "/administration/users/"), | ||||
|         new SidebarItem("Groups", "/administration/groups/") | ||||
|     ).when((): Promise<boolean> => { | ||||
|         return User.me().then(u => u.is_superuser); | ||||
|     }), | ||||
| ]; | ||||
|  | ||||
| @customElement("ak-interface-admin") | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| import { gettext } from "django"; | ||||
| import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { ifDefined } from "lit-html/directives/if-defined"; | ||||
| import { Application } from "../api/application"; | ||||
| import { PBResponse } from "../api/client"; | ||||
| import { Application } from "../api/Applications"; | ||||
| import { PBResponse } from "../api/Client"; | ||||
| import { COMMON_STYLES } from "../common/styles"; | ||||
| import { loading, truncate } from "../utils"; | ||||
|  | ||||
|  | ||||
| @ -1,71 +1,27 @@ | ||||
| import { gettext } from "django"; | ||||
| import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { AdminOverview } from "../../api/admin_overview"; | ||||
| import { DefaultClient } from "../../api/client"; | ||||
| import { User } from "../../api/user"; | ||||
| import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element"; | ||||
| import { DefaultClient } from "../../api/Client"; | ||||
| import { COMMON_STYLES } from "../../common/styles"; | ||||
| import { AggregatePromiseCard } from "../../elements/cards/AggregatePromiseCard"; | ||||
| import { SpinnerSize } from "../../elements/Spinner"; | ||||
|  | ||||
| import "../../elements/AdminLoginsChart"; | ||||
| import "../../elements/cards/AggregatePromiseCard"; | ||||
| import "./TopApplicationsTable"; | ||||
|  | ||||
| @customElement("ak-admin-status-card") | ||||
| export class AdminStatusCard extends AggregatePromiseCard { | ||||
|  | ||||
|     @property({type: Number}) | ||||
|     value?: number; | ||||
|  | ||||
|     @property() | ||||
|     warningText?: string; | ||||
|  | ||||
|     @property({type: Number}) | ||||
|     lessThanThreshold?: number; | ||||
|  | ||||
|     renderNone(): TemplateResult { | ||||
|         return html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`; | ||||
|     } | ||||
|  | ||||
|     renderGood(): TemplateResult { | ||||
|         return html`<p class="ak-aggregate-card"> | ||||
|             <i class="fa fa-check-circle"></i> ${this.value} | ||||
|         </p>`; | ||||
|     } | ||||
|  | ||||
|     renderBad(): TemplateResult { | ||||
|         return html`<p class="ak-aggregate-card"> | ||||
|             <i class="fa fa-exclamation-triangle"></i> ${this.value} | ||||
|         </p> | ||||
|         <p class="subtext">${this.warningText ? gettext(this.warningText) : ""}</p>`; | ||||
|     } | ||||
|  | ||||
|     renderInner(): TemplateResult { | ||||
|         if (!this.value) { | ||||
|             return this.renderNone(); | ||||
|         } | ||||
|  | ||||
|         return html``; | ||||
|     } | ||||
|  | ||||
| } | ||||
| import "./cards/AdminStatusCard"; | ||||
| import "./cards/FlowCacheStatusCard"; | ||||
| import "./cards/PolicyCacheStatusCard"; | ||||
| import "./cards/PolicyUnboundStatusCard"; | ||||
| import "./cards/ProviderStatusCard"; | ||||
| import "./cards/UserCountStatusCard"; | ||||
| import "./cards/VersionStatusCard"; | ||||
| import "./cards/WorkerStatusCard"; | ||||
|  | ||||
| @customElement("ak-admin-overview") | ||||
| export class AdminOverviewPage extends LitElement { | ||||
|     @property({attribute: false}) | ||||
|     data?: AdminOverview; | ||||
|  | ||||
|     @property({attribute: false}) | ||||
|     users?: Promise<number>; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return COMMON_STYLES; | ||||
|     } | ||||
|  | ||||
|     firstUpdated(): void { | ||||
|         AdminOverview.get().then(value => this.data = value); | ||||
|         this.users = User.count(); | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<section class="pf-c-page__main-section pf-m-light"> | ||||
|             <div class="pf-c-content"> | ||||
| @ -80,48 +36,20 @@ export class AdminOverviewPage extends LitElement { | ||||
|                 <ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;"> | ||||
|                     <ak-top-applications-table></ak-top-applications-table> | ||||
|                 </ak-aggregate-card> | ||||
|                 <ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Workers"> | ||||
|                     ${this.data ? | ||||
|         this.data?.worker_count < 1 ? | ||||
|             html`<p class="ak-aggregate-card"> | ||||
|                                     <i class="fa fa-exclamation-triangle"></i> ${this.data?.worker_count} | ||||
|                                 </p> | ||||
|                                 <p class="subtext">${gettext("No workers connected.")}</p>` : | ||||
|             html`<p class="ak-aggregate-card"> | ||||
|                                     <i class="fa fa-check-circle"></i> ${this.data?.worker_count} | ||||
|                                 </p>` | ||||
|         : html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`} | ||||
|                 </ak-aggregate-card> | ||||
|                 <ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Providers" headerLink="#/administration/providers/"> | ||||
|                     ${this.data ? | ||||
|         this.data?.providers_without_application > 1 ? | ||||
|             html`<p class="ak-aggregate-card"> | ||||
|                                     <i class="fa fa-exclamation-triangle"></i> 0 | ||||
|                                 </p> | ||||
|                                 <p class="subtext">${gettext("At least one Provider has no application assigned.")}</p>` : | ||||
|             html`<p class="ak-aggregate-card"> | ||||
|                                     <i class="fa fa-check-circle"></i> 0 | ||||
|                                 </p>` | ||||
|         : html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`} | ||||
|                 </ak-aggregate-card> | ||||
|                 <ak-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Policies" headerLink="#/administration/policies/"> | ||||
|                     ${this.data ? | ||||
|         this.data?.policies_without_binding > 1 ? | ||||
|             html`<p class="ak-aggregate-card"> | ||||
|                                     <i class="fa fa-exclamation-triangle"></i> 0 | ||||
|                                 </p> | ||||
|                                 <p class="subtext">${gettext("Policies without binding exist.")}</p>` : | ||||
|             html`<p class="ak-aggregate-card"> | ||||
|                                     <i class="fa fa-check-circle"></i> 0 | ||||
|                                 </p>` | ||||
|         : html`<ak-spinner size=${SpinnerSize.Large}></ak-spinner>`} | ||||
|                 </ak-aggregate-card> | ||||
|                 <ak-aggregate-card-promise | ||||
|                     icon="pf-icon pf-icon-user" | ||||
|                     header="Users" | ||||
|                     headerLink="#/administration/users/" | ||||
|                     .promise=${this.users}> | ||||
|                 </ak-aggregate-card-promise> | ||||
|                 <ak-admin-status-card-provider class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Providers" headerLink="#/administration/providers/"> | ||||
|                 </ak-admin-status-card-provider> | ||||
|                 <ak-admin-status-card-policy-unbound class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-infrastructure" header="Policies" headerLink="#/administration/policies/"> | ||||
|                 </ak-admin-status-card-policy-unbound> | ||||
|                 <ak-admin-status-card-user-count class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-user" header="Users" headerLink="#/administration/users/"> | ||||
|                 </ak-admin-status-card-user-count> | ||||
|                 <ak-admin-status-version class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-bundle" header="Version" headerLink="https://github.com/BeryJu/authentik/releases"> | ||||
|                 </ak-admin-status-version> | ||||
|                 <ak-admin-status-card-workers class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Workers"> | ||||
|                 </ak-admin-status-card-workers> | ||||
|                 <ak-admin-status-card-policy-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Cached Policies"> | ||||
|                 </ak-admin-status-card-policy-cache> | ||||
|                 <ak-admin-status-card-flow-cache class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Cached Flows"> | ||||
|                 </ak-admin-status-card-flow-cache> | ||||
|             </div> | ||||
|         </section>`; | ||||
|     } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { gettext } from "django"; | ||||
| import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { AuditEvent, TopNEvent } from "../../api/events"; | ||||
| import { AuditEvent, TopNEvent } from "../../api/Events"; | ||||
| import { COMMON_STYLES } from "../../common/styles"; | ||||
|  | ||||
| import "../../elements/Spinner"; | ||||
|  | ||||
							
								
								
									
										37
									
								
								web/src/pages/admin-overview/cards/AdminStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								web/src/pages/admin-overview/cards/AdminStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| import { html, TemplateResult } from "lit-html"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
| import { AggregateCard } from "../../../elements/cards/AggregateCard"; | ||||
| import { SpinnerSize } from "../../../elements/Spinner"; | ||||
|  | ||||
| export interface AdminStatus { | ||||
|     icon: string; | ||||
|     message?: string; | ||||
| } | ||||
|  | ||||
| export abstract class AdminStatusCard<T> extends AggregateCard { | ||||
|  | ||||
|     abstract getPrimaryValue(): Promise<T>; | ||||
|  | ||||
|     abstract getStatus(value: T): Promise<AdminStatus>; | ||||
|  | ||||
|     value?: T; | ||||
|  | ||||
|     renderValue(): TemplateResult { | ||||
|         return html`${this.value}`; | ||||
|     } | ||||
|  | ||||
|     renderInner(): TemplateResult { | ||||
|         return html`<p class="center-value"> | ||||
|             ${until(this.getPrimaryValue().then((v) => { | ||||
|         this.value = v; | ||||
|         return this.getStatus(v); | ||||
|     }).then((status) => { | ||||
|         return html`<p class="ak-aggregate-card"> | ||||
|                     <i class="${status.icon}"></i> ${this.renderValue()} | ||||
|                 </p> | ||||
|                 ${status.message ? html`<p class="subtext">${status.message}</p>` : html``}`; | ||||
|     }), html`<ak-spinner size="${SpinnerSize.Large}"></ak-spinner>`)} | ||||
|         </p>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										36
									
								
								web/src/pages/admin-overview/cards/FlowCacheStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								web/src/pages/admin-overview/cards/FlowCacheStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement, html, TemplateResult } from "lit-element"; | ||||
| import { Flow } from "../../../api/Flows"; | ||||
| import { AdminStatus, AdminStatusCard } from "./AdminStatusCard"; | ||||
| import "../../../elements/buttons/ModalButton"; | ||||
|  | ||||
| @customElement("ak-admin-status-card-flow-cache") | ||||
| export class FlowCacheStatusCard extends AdminStatusCard<number> { | ||||
|  | ||||
|     getPrimaryValue(): Promise<number> { | ||||
|         return Flow.cached(); | ||||
|     } | ||||
|  | ||||
|     getStatus(value: number): Promise<AdminStatus> { | ||||
|         if (value < 1) { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-exclamation-triangle pf-m-warning", | ||||
|                 message: gettext("No flows cached."), | ||||
|             }); | ||||
|         } else { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-check-circle pf-m-success" | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     renderHeaderLink(): TemplateResult { | ||||
|         return html`<ak-modal-button href="/administration/overview/cache/flow/"> | ||||
|             <a slot="trigger"> | ||||
|                 <i class="fa fa-trash"> </i> | ||||
|             </a> | ||||
|             <div slot="modal"></div> | ||||
|         </ak-modal-button>`; | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										37
									
								
								web/src/pages/admin-overview/cards/PolicyCacheStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								web/src/pages/admin-overview/cards/PolicyCacheStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement } from "lit-element"; | ||||
| import { TemplateResult, html } from "lit-html"; | ||||
| import { Policy } from "../../../api/Policies"; | ||||
| import { AdminStatusCard, AdminStatus } from "./AdminStatusCard"; | ||||
| import "../../../elements/buttons/ModalButton"; | ||||
|  | ||||
| @customElement("ak-admin-status-card-policy-cache") | ||||
| export class PolicyCacheStatusCard extends AdminStatusCard<number> { | ||||
|  | ||||
|     getPrimaryValue(): Promise<number> { | ||||
|         return Policy.cached(); | ||||
|     } | ||||
|  | ||||
|     getStatus(value: number): Promise<AdminStatus> { | ||||
|         if (value < 1) { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-exclamation-triangle pf-m-warning", | ||||
|                 message: gettext("No policies cached. Users may experience slow response times."), | ||||
|             }); | ||||
|         } else { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-check-circle pf-m-success" | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     renderHeaderLink(): TemplateResult { | ||||
|         return html`<ak-modal-button href="/administration/overview/cache/policy/"> | ||||
|             <a slot="trigger"> | ||||
|                 <i class="fa fa-trash"> </i> | ||||
|             </a> | ||||
|             <div slot="modal"></div> | ||||
|         </ak-modal-button>`; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement } from "lit-element"; | ||||
| import { Policy } from "../../../api/Policies"; | ||||
| import { AdminStatusCard, AdminStatus } from "./AdminStatusCard"; | ||||
|  | ||||
| @customElement("ak-admin-status-card-policy-unbound") | ||||
| export class PolicyUnboundStatusCard extends AdminStatusCard<number> { | ||||
|  | ||||
|     getPrimaryValue(): Promise<number> { | ||||
|         return Policy.list({ | ||||
|             "bindings__isnull": true, | ||||
|             "promptstage__isnull": true, | ||||
|         }).then((response) => { | ||||
|             return response.pagination.count; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     getStatus(value: number): Promise<AdminStatus> { | ||||
|         if (value > 0) { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-exclamation-triangle pf-m-warning", | ||||
|                 message: gettext("Policies without binding exist."), | ||||
|             }); | ||||
|         } else { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-check-circle pf-m-success" | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										30
									
								
								web/src/pages/admin-overview/cards/ProviderStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								web/src/pages/admin-overview/cards/ProviderStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement } from "lit-element"; | ||||
| import { Provider } from "../../../api/Providers"; | ||||
| import { AdminStatusCard, AdminStatus } from "./AdminStatusCard"; | ||||
|  | ||||
| @customElement("ak-admin-status-card-provider") | ||||
| export class ProviderStatusCard extends AdminStatusCard<number> { | ||||
|  | ||||
|     getPrimaryValue(): Promise<number> { | ||||
|         return Provider.list({ | ||||
|             "application__isnull": true | ||||
|         }).then((response) => { | ||||
|             return response.pagination.count; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     getStatus(value: number): Promise<AdminStatus> { | ||||
|         if (value > 0) { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-exclamation-triangle pf-m-warning", | ||||
|                 message: gettext("Warning: At least one Provider has no application assigned."), | ||||
|             }); | ||||
|         } else { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-check-circle pf-m-success" | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										19
									
								
								web/src/pages/admin-overview/cards/UserCountStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								web/src/pages/admin-overview/cards/UserCountStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| import { customElement } from "lit-element"; | ||||
| import { User } from "../../../api/Users"; | ||||
| import { AdminStatusCard, AdminStatus } from "./AdminStatusCard"; | ||||
|  | ||||
| @customElement("ak-admin-status-card-user-count") | ||||
| export class UserCountStatusCard extends AdminStatusCard<number> { | ||||
|  | ||||
|     getPrimaryValue(): Promise<number> { | ||||
|         return User.count(); | ||||
|     } | ||||
|  | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|     getStatus(value: number): Promise<AdminStatus> { | ||||
|         return Promise.resolve<AdminStatus>({ | ||||
|             icon: "fa fa-check-circle pf-m-success" | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										31
									
								
								web/src/pages/admin-overview/cards/VersionStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/src/pages/admin-overview/cards/VersionStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement, html, TemplateResult } from "lit-element"; | ||||
| import { Version } from "../../../api/Versions"; | ||||
| import { AdminStatusCard, AdminStatus } from "./AdminStatusCard"; | ||||
|  | ||||
| @customElement("ak-admin-status-version") | ||||
| export class VersionStatusCard extends AdminStatusCard<Version> { | ||||
|  | ||||
|     getPrimaryValue(): Promise<Version> { | ||||
|         return Version.get(); | ||||
|     } | ||||
|  | ||||
|     getStatus(value: Version): Promise<AdminStatus> { | ||||
|         if (value.outdated) { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-exclamation-triangle pf-m-warning", | ||||
|                 message: gettext(`${value.version_latest} is available!`), | ||||
|             }); | ||||
|         } else { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-check-circle pf-m-success", | ||||
|                 message: gettext("Up-to-date!") | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     renderValue(): TemplateResult { | ||||
|         return html`${this.value?.version_current}`; | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										28
									
								
								web/src/pages/admin-overview/cards/WorkerStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								web/src/pages/admin-overview/cards/WorkerStatusCard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement } from "lit-element"; | ||||
| import { DefaultClient, PBResponse } from "../../../api/Client"; | ||||
| import { AdminStatus, AdminStatusCard } from "./AdminStatusCard"; | ||||
|  | ||||
| @customElement("ak-admin-status-card-workers") | ||||
| export class WorkersStatusCard extends AdminStatusCard<number> { | ||||
|  | ||||
|     getPrimaryValue(): Promise<number> { | ||||
|         return DefaultClient.fetch<PBResponse<number>>(["admin", "workers"]).then((r) => { | ||||
|             return r.pagination.count; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     getStatus(value: number): Promise<AdminStatus> { | ||||
|         if (value < 1) { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-exclamation-triangle pf-m-warning", | ||||
|                 message: gettext("No workers connected. Background tasks will not run."), | ||||
|             }); | ||||
|         } else { | ||||
|             return Promise.resolve<AdminStatus>({ | ||||
|                 icon: "fa fa-check-circle pf-m-success" | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -1,7 +1,7 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement, html, TemplateResult } from "lit-element"; | ||||
| import { Application } from "../../api/application"; | ||||
| import { PBResponse } from "../../api/client"; | ||||
| import { Application } from "../../api/Applications"; | ||||
| import { PBResponse } from "../../api/Client"; | ||||
| import { TablePage } from "../../elements/table/TablePage"; | ||||
|  | ||||
| import "../../elements/buttons/ModalButton"; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { gettext } from "django"; | ||||
| import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { Application } from "../../api/application"; | ||||
| import { DefaultClient } from "../../api/client"; | ||||
| import { Application } from "../../api/Applications"; | ||||
| import { DefaultClient } from "../../api/Client"; | ||||
| import { COMMON_STYLES } from "../../common/styles"; | ||||
|  | ||||
| import "../../elements/Tabs"; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { gettext } from "django"; | ||||
| import { customElement, html, property, TemplateResult } from "lit-element"; | ||||
| import { PBResponse } from "../../api/client"; | ||||
| import { PBResponse } from "../../api/Client"; | ||||
| import { Table } from "../../elements/table/Table"; | ||||
|  | ||||
| import "../../elements/Tabs"; | ||||
| @ -8,7 +8,7 @@ import "../../elements/AdminLoginsChart"; | ||||
| import "../../elements/buttons/ModalButton"; | ||||
| import "../../elements/buttons/SpinnerButton"; | ||||
| import "../../elements/policies/BoundPoliciesList"; | ||||
| import { FlowStageBinding } from "../../api/flow"; | ||||
| import { FlowStageBinding } from "../../api/Flows"; | ||||
|  | ||||
| @customElement("ak-bound-stages-list") | ||||
| export class BoundStagesList extends Table<FlowStageBinding> { | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { gettext } from "django"; | ||||
| import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||
| import { COMMON_STYLES } from "../../common/styles"; | ||||
| import { Flow } from "../../api/flow"; | ||||
| import { Flow } from "../../api/Flows"; | ||||
|  | ||||
| import "../../elements/Tabs"; | ||||
| import "../../elements/AdminLoginsChart"; | ||||
|  | ||||
| @ -7,7 +7,7 @@ import "../../elements/AdminLoginsChart"; | ||||
| import "../../elements/buttons/ModalButton"; | ||||
| import "../../elements/buttons/SpinnerButton"; | ||||
| import "../../elements/policies/BoundPoliciesList"; | ||||
| import { Source } from "../../api/source"; | ||||
| import { Source } from "../../api/Sources"; | ||||
|  | ||||
| @customElement("ak-source-view") | ||||
| export class SourceViewPage extends LitElement { | ||||
|  | ||||
| @ -13,7 +13,7 @@ export const ROUTES: Route[] = [ | ||||
|     new Route(new RegExp("^/$")).redirect("/library/"), | ||||
|     new Route(new RegExp("^#.*")).redirect("/library/"), | ||||
|     new Route(new RegExp("^/library/$"), html`<ak-library></ak-library>`), | ||||
|     new Route(new RegExp("^/administration/overview-ng/$"), html`<ak-admin-overview></ak-admin-overview>`), | ||||
|     new Route(new RegExp("^/administration/overview/$"), html`<ak-admin-overview></ak-admin-overview>`), | ||||
|     new Route(new RegExp("^/applications/$"), html`<ak-application-list></ak-application-list>`), | ||||
|     new Route(new RegExp(`^/applications/(?<slug>${SLUG_REGEX})/$`)).then((args) => { | ||||
|         return html`<ak-application-view .args=${args}></ak-application-view>`; | ||||
|  | ||||
| @ -15,7 +15,7 @@ Download the latest `docker-compose.yml` from [here](https://raw.githubuserconte | ||||
|  | ||||
| To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env` | ||||
|  | ||||
| To optionally deploy a different version run `echo AUTHENTIK_TAG=0.13.1-stable >> .env` | ||||
| To optionally deploy a different version run `echo AUTHENTIK_TAG=0.13.2-stable >> .env` | ||||
|  | ||||
| If this is a fresh authentik install run the following commands to generate a password: | ||||
|  | ||||
|  | ||||
| @ -22,7 +22,7 @@ image: | ||||
|     name: beryju/authentik | ||||
|     name_static: beryju/authentik-static | ||||
|     name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended | ||||
|     tag: 0.13.1-stable | ||||
|     tag: 0.13.2-stable | ||||
|  | ||||
| serverReplicas: 1 | ||||
| workerReplicas: 1 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	