web: add outpost list page
This commit is contained in:
		| @ -12,10 +12,7 @@ from django.utils.translation import gettext as _ | |||||||
| from django.views.generic import UpdateView | from django.views.generic import UpdateView | ||||||
| from guardian.mixins import PermissionRequiredMixin | from guardian.mixins import PermissionRequiredMixin | ||||||
|  |  | ||||||
| from authentik.admin.views.utils import ( | from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView | ||||||
|     BackSuccessUrlMixin, |  | ||||||
|     DeleteMessageView, |  | ||||||
| ) |  | ||||||
| from authentik.lib.views import CreateAssignPermView | from authentik.lib.views import CreateAssignPermView | ||||||
| from authentik.outposts.forms import OutpostForm | from authentik.outposts.forms import OutpostForm | ||||||
| from authentik.outposts.models import Outpost, OutpostConfig | from authentik.outposts.models import Outpost, OutpostConfig | ||||||
|  | |||||||
| @ -51,6 +51,13 @@ class OutpostViewSet(ModelViewSet): | |||||||
|  |  | ||||||
|     queryset = Outpost.objects.all() |     queryset = Outpost.objects.all() | ||||||
|     serializer_class = OutpostSerializer |     serializer_class = OutpostSerializer | ||||||
|  |     filterset_fields = { | ||||||
|  |         "providers": ["isnull"], | ||||||
|  |     } | ||||||
|  |     search_fields = [ | ||||||
|  |         "name", | ||||||
|  |         "providers__name", | ||||||
|  |     ] | ||||||
|  |  | ||||||
|     @swagger_auto_schema(responses={200: OutpostHealthSerializer(many=True)}) |     @swagger_auto_schema(responses={200: OutpostHealthSerializer(many=True)}) | ||||||
|     @action(methods=["GET"], detail=True) |     @action(methods=["GET"], detail=True) | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								swagger.yaml
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								swagger.yaml
									
									
									
									
									
								
							| @ -1789,6 +1789,11 @@ paths: | |||||||
|       operationId: outposts_outposts_list |       operationId: outposts_outposts_list | ||||||
|       description: Outpost Viewset |       description: Outpost Viewset | ||||||
|       parameters: |       parameters: | ||||||
|  |         - name: providers__isnull | ||||||
|  |           in: query | ||||||
|  |           description: '' | ||||||
|  |           required: false | ||||||
|  |           type: string | ||||||
|         - name: ordering |         - name: ordering | ||||||
|           in: query |           in: query | ||||||
|           description: Which field to use when ordering the results. |           description: Which field to use when ordering the results. | ||||||
| @ -1914,11 +1919,11 @@ paths: | |||||||
|   /outposts/outposts/{uuid}/health/: |   /outposts/outposts/{uuid}/health/: | ||||||
|     get: |     get: | ||||||
|       operationId: outposts_outposts_health |       operationId: outposts_outposts_health | ||||||
|       description: Outpost Viewset |       description: Get outposts current health | ||||||
|       parameters: [] |       parameters: [] | ||||||
|       responses: |       responses: | ||||||
|         '200': |         '200': | ||||||
|           description: '' |           description: Outpost health status | ||||||
|           schema: |           schema: | ||||||
|             description: '' |             description: '' | ||||||
|             type: array |             type: array | ||||||
| @ -4457,7 +4462,7 @@ paths: | |||||||
|   /providers/oauth2/{id}/setup_urls/: |   /providers/oauth2/{id}/setup_urls/: | ||||||
|     get: |     get: | ||||||
|       operationId: providers_oauth2_setup_urls |       operationId: providers_oauth2_setup_urls | ||||||
|       description: Return metadata as XML string |       description: Get Providers setup URLs | ||||||
|       parameters: [] |       parameters: [] | ||||||
|       responses: |       responses: | ||||||
|         '200': |         '200': | ||||||
| @ -8179,11 +8184,15 @@ definitions: | |||||||
|         type: string |         type: string | ||||||
|         format: uuid |         format: uuid | ||||||
|         x-nullable: true |         x-nullable: true | ||||||
|  |       token_identifier: | ||||||
|  |         title: Token identifier | ||||||
|  |         type: string | ||||||
|  |         readOnly: true | ||||||
|       _config: |       _config: | ||||||
|         title: config |         title: config | ||||||
|         type: object |         type: object | ||||||
|   OutpostHealth: |   OutpostHealth: | ||||||
|     description: '' |     description: Outpost health status | ||||||
|     type: object |     type: object | ||||||
|     properties: |     properties: | ||||||
|       last_seen: |       last_seen: | ||||||
| @ -8191,6 +8200,20 @@ definitions: | |||||||
|         type: string |         type: string | ||||||
|         format: date-time |         format: date-time | ||||||
|         readOnly: true |         readOnly: true | ||||||
|  |       version: | ||||||
|  |         title: Version | ||||||
|  |         type: string | ||||||
|  |         readOnly: true | ||||||
|  |         minLength: 1 | ||||||
|  |       version_should: | ||||||
|  |         title: Version should | ||||||
|  |         type: string | ||||||
|  |         readOnly: true | ||||||
|  |         minLength: 1 | ||||||
|  |       version_outdated: | ||||||
|  |         title: Version outdated | ||||||
|  |         type: boolean | ||||||
|  |         readOnly: true | ||||||
|   OpenIDConnectConfiguration: |   OpenIDConnectConfiguration: | ||||||
|     title: Oidc configuration |     title: Oidc configuration | ||||||
|     description: rest_framework Serializer for OIDC Configuration |     description: rest_framework Serializer for OIDC Configuration | ||||||
|  | |||||||
							
								
								
									
										39
									
								
								web/src/api/Outposts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								web/src/api/Outposts.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | import { DefaultClient, PBResponse, QueryArguments } from "./Client"; | ||||||
|  | import { Provider } from "./Providers"; | ||||||
|  |  | ||||||
|  | export interface OutpostHealth { | ||||||
|  |     last_seen: number; | ||||||
|  |     version: string; | ||||||
|  |     version_should: string; | ||||||
|  |     version_outdated: boolean; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class Outpost { | ||||||
|  |  | ||||||
|  |     pk: string; | ||||||
|  |     name: string; | ||||||
|  |     providers: Provider[]; | ||||||
|  |     service_connection?: string; | ||||||
|  |     _config: QueryArguments; | ||||||
|  |     token_identifier: string; | ||||||
|  |  | ||||||
|  |     constructor() { | ||||||
|  |         throw Error(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static get(pk: string): Promise<Outpost> { | ||||||
|  |         return DefaultClient.fetch<Outpost>(["outposts", "outposts", pk]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static list(filter?: QueryArguments): Promise<PBResponse<Outpost>> { | ||||||
|  |         return DefaultClient.fetch<PBResponse<Outpost>>(["outposts", "outposts"], filter); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static health(pk: string): Promise<OutpostHealth[]> { | ||||||
|  |         return DefaultClient.fetch<OutpostHealth[]>(["outposts", "outposts", pk, "health"]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static adminUrl(rest: string): string { | ||||||
|  |         return `/administration/outposts/${rest}`; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -27,7 +27,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ | |||||||
|             `^/sources/(?<slug>${SLUG_REGEX})$`, |             `^/sources/(?<slug>${SLUG_REGEX})$`, | ||||||
|         ), |         ), | ||||||
|         new SidebarItem("Providers", "/providers"), |         new SidebarItem("Providers", "/providers"), | ||||||
|         new SidebarItem("Outposts", "/administration/outposts/"), |         new SidebarItem("Outposts", "/outposts"), | ||||||
|         new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"), |         new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"), | ||||||
|     ).when((): Promise<boolean> => { |     ).when((): Promise<boolean> => { | ||||||
|         return User.me().then(u => u.is_superuser); |         return User.me().then(u => u.is_superuser); | ||||||
|  | |||||||
							
								
								
									
										49
									
								
								web/src/pages/outposts/OutpostHealth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								web/src/pages/outposts/OutpostHealth.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | import { gettext } from "django"; | ||||||
|  | import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; | ||||||
|  | import { until } from "lit-html/directives/until"; | ||||||
|  | import { Outpost } from "../../api/Outposts"; | ||||||
|  | import { COMMON_STYLES } from "../../common/styles"; | ||||||
|  |  | ||||||
|  | @customElement("ak-outpost-health") | ||||||
|  | export class OutpostHealth extends LitElement { | ||||||
|  |  | ||||||
|  |     @property() | ||||||
|  |     outpostId?: string; | ||||||
|  |  | ||||||
|  |     static get styles(): CSSResult[] { | ||||||
|  |         return COMMON_STYLES; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     render(): TemplateResult { | ||||||
|  |         if (!this.outpostId) { | ||||||
|  |             return html`<ak-spinner></ak-spinner>`; | ||||||
|  |         } | ||||||
|  |         return html`<ul>${until(Outpost.health(this.outpostId).then((oh) => { | ||||||
|  |             if (oh.length === 0) { | ||||||
|  |                 return html`<li> | ||||||
|  |                     <ul> | ||||||
|  |                         <li role="cell"> | ||||||
|  |                             <i class="fas fa-question-circle"></i> ${gettext("Not available")} | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                 </li>`; | ||||||
|  |             } | ||||||
|  |             return oh.map((h) => { | ||||||
|  |                 return html`<li> | ||||||
|  |                     <ul> | ||||||
|  |                         <li role="cell"> | ||||||
|  |                             <i class="fas fa-check pf-m-success"></i> ${gettext(`Last seen: ${new Date(h.last_seen * 1000).toLocaleTimeString()}`)} | ||||||
|  |                         </li> | ||||||
|  |                         <li role="cell"> | ||||||
|  |                             ${h.version_outdated ? | ||||||
|  |                             html`<i class="fas fa-times pf-m-danger"></i>  | ||||||
|  |                                 ${gettext(`${h.version}, should be ${h.version_should}`)}` : | ||||||
|  |                             html`<i class="fas fa-check pf-m-success"></i> ${gettext(`Version: ${h.version}`)}`} | ||||||
|  |                         </li> | ||||||
|  |                     </ul> | ||||||
|  |                 </li>`; | ||||||
|  |             }); | ||||||
|  |         }), html`<ak-spinner></ak-spinner>`)}</ul>`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										121
									
								
								web/src/pages/outposts/OutpostListPage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								web/src/pages/outposts/OutpostListPage.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | |||||||
|  | import { gettext } from "django"; | ||||||
|  | import { customElement, property } from "lit-element"; | ||||||
|  | import { html, TemplateResult } from "lit-html"; | ||||||
|  | import { PBResponse } from "../../api/Client"; | ||||||
|  | import { Outpost } from "../../api/Outposts"; | ||||||
|  | import { TableColumn } from "../../elements/table/Table"; | ||||||
|  | import { TablePage } from "../../elements/table/TablePage"; | ||||||
|  |  | ||||||
|  | import "./OutpostHealth"; | ||||||
|  | import "../../elements/buttons/SpinnerButton"; | ||||||
|  | import "../../elements/buttons/ModalButton"; | ||||||
|  |  | ||||||
|  | @customElement("ak-outpost-list") | ||||||
|  | export class OutpostListPage extends TablePage<Outpost> { | ||||||
|  |     pageTitle(): string { | ||||||
|  |         return "Outposts"; | ||||||
|  |     } | ||||||
|  |     pageDescription(): string | undefined { | ||||||
|  |         return "Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies."; | ||||||
|  |     } | ||||||
|  |     pageIcon(): string { | ||||||
|  |         return "pf-icon pf-icon-zone"; | ||||||
|  |     } | ||||||
|  |     searchEnabled(): boolean { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     apiEndpoint(page: number): Promise<PBResponse<Outpost>> { | ||||||
|  |         return Outpost.list({ | ||||||
|  |             ordering: this.order, | ||||||
|  |             page: page, | ||||||
|  |             search: this.search || "", | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     columns(): TableColumn[] { | ||||||
|  |         return [ | ||||||
|  |             new TableColumn("Name", "name"), | ||||||
|  |             new TableColumn("Providers"), | ||||||
|  |             new TableColumn("Health and Version"), | ||||||
|  |             new TableColumn(""), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @property() | ||||||
|  |     order = "name"; | ||||||
|  |  | ||||||
|  |     row(item: Outpost): TemplateResult[] { | ||||||
|  |         return [ | ||||||
|  |             html`${item.name}`, | ||||||
|  |             html`<ul>${item.providers.map((p) => { | ||||||
|  |                 return html`<li><a href="#/providers/${p.pk}">${p.name}</a></li>`; | ||||||
|  |             })}</ul>`, | ||||||
|  |             html`<ak-outpost-health outpostId=${item.pk}></ak-outpost-health>`, | ||||||
|  |             html` | ||||||
|  |             <ak-modal-button href="${Outpost.adminUrl(`${item.pk}/update`)}"> | ||||||
|  |                 <ak-spinner-button slot="trigger" class="pf-m-secondary"> | ||||||
|  |                     ${gettext("Edit")} | ||||||
|  |                 </ak-spinner-button> | ||||||
|  |                 <div slot="modal"></div> | ||||||
|  |             </ak-modal-button> | ||||||
|  |             <ak-modal-button href="${Outpost.adminUrl(`${item.pk}/delete`)}"> | ||||||
|  |                 <ak-spinner-button slot="trigger" class="pf-m-danger"> | ||||||
|  |                     ${gettext("Delete")} | ||||||
|  |                 </ak-spinner-button> | ||||||
|  |                 <div slot="modal"></div> | ||||||
|  |             </ak-modal-button> | ||||||
|  |             <ak-modal-button> | ||||||
|  |                 <button slot="trigger" class="pf-c-button pf-m-tertiary"> | ||||||
|  |                     ${gettext('View Deployment Info')} | ||||||
|  |                 </button> | ||||||
|  |                 <div slot="modal"> | ||||||
|  |                     <div class="pf-c-modal-box__header"> | ||||||
|  |                         <h1 class="pf-c-title pf-m-2xl" id="modal-title">${gettext('Outpost Deployment Info')}</h1> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="pf-c-modal-box__body" id="modal-description"> | ||||||
|  |                         <p><a href="https://goauthentik.io/docs/outposts/outposts/#deploy">${gettext('View deployment documentation')}</a></p> | ||||||
|  |                         <form class="pf-c-form"> | ||||||
|  |                             <div class="pf-c-form__group"> | ||||||
|  |                                 <label class="pf-c-form__label" for="help-text-simple-form-name"> | ||||||
|  |                                     <span class="pf-c-form__label-text">AUTHENTIK_HOST</span> | ||||||
|  |                                 </label> | ||||||
|  |                                 <input class="pf-c-form-control" readonly type="text" value="${document.location.toString()}" /> | ||||||
|  |                             </div> | ||||||
|  |                             <div class="pf-c-form__group"> | ||||||
|  |                                 <label class="pf-c-form__label" for="help-text-simple-form-name"> | ||||||
|  |                                     <span class="pf-c-form__label-text">AUTHENTIK_TOKEN</span> | ||||||
|  |                                 </label> | ||||||
|  |                                 <div> | ||||||
|  |                                     <ak-token-copy-button identifier="${item.token_identifier}"> | ||||||
|  |                                         ${gettext('Click to copy token')} | ||||||
|  |                                     </ak-token-copy-button> | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                             <h3>${gettext('If your authentik Instance is using a self-signed certificate, set this value.')}</h3> | ||||||
|  |                             <div class="pf-c-form__group"> | ||||||
|  |                                 <label class="pf-c-form__label" for="help-text-simple-form-name"> | ||||||
|  |                                     <span class="pf-c-form__label-text">AUTHENTIK_INSECURE</span> | ||||||
|  |                                 </label> | ||||||
|  |                                 <input class="pf-c-form-control" readonly type="text" value="true" /> | ||||||
|  |                             </div> | ||||||
|  |                         </form> | ||||||
|  |                     </div> | ||||||
|  |                     <footer class="pf-c-modal-box__footer pf-m-align-left"> | ||||||
|  |                         <a class="pf-c-button pf-m-primary">${gettext('Close')}</a> | ||||||
|  |                     </footer> | ||||||
|  |                 </div> | ||||||
|  |             </ak-modal-button>`, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     renderToolbar(): TemplateResult { | ||||||
|  |         return html` | ||||||
|  |         <ak-modal-button href=${Outpost.adminUrl("create/")}> | ||||||
|  |             <ak-spinner-button slot="trigger" class="pf-m-primary"> | ||||||
|  |                 ${gettext("Create")} | ||||||
|  |             </ak-spinner-button> | ||||||
|  |             <div slot="modal"></div> | ||||||
|  |         </ak-modal-button> | ||||||
|  |         ${super.renderToolbar()} | ||||||
|  |         `; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -14,6 +14,7 @@ import "./pages/events/RuleListPage"; | |||||||
| import "./pages/providers/ProviderListPage"; | import "./pages/providers/ProviderListPage"; | ||||||
| import "./pages/providers/ProviderViewPage"; | import "./pages/providers/ProviderViewPage"; | ||||||
| import "./pages/property-mappings/PropertyMappingListPage"; | import "./pages/property-mappings/PropertyMappingListPage"; | ||||||
|  | import "./pages/outposts/OutpostListPage"; | ||||||
|  |  | ||||||
| export const ROUTES: Route[] = [ | export const ROUTES: Route[] = [ | ||||||
|     // Prevent infinite Shell loops |     // Prevent infinite Shell loops | ||||||
| @ -42,4 +43,5 @@ export const ROUTES: Route[] = [ | |||||||
|     new Route(new RegExp("^/events/transports$"), html`<ak-event-transport-list></ak-event-transport-list>`), |     new Route(new RegExp("^/events/transports$"), html`<ak-event-transport-list></ak-event-transport-list>`), | ||||||
|     new Route(new RegExp("^/events/rules$"), html`<ak-event-rule-list></ak-event-rule-list>`), |     new Route(new RegExp("^/events/rules$"), html`<ak-event-rule-list></ak-event-rule-list>`), | ||||||
|     new Route(new RegExp("^/property-mappings$"), html`<ak-property-mapping-list></ak-property-mapping-list>`), |     new Route(new RegExp("^/property-mappings$"), html`<ak-property-mapping-list></ak-property-mapping-list>`), | ||||||
|  |     new Route(new RegExp("^/outposts$"), html`<ak-outpost-list></ak-outpost-list>`), | ||||||
| ]; | ]; | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer