web: add outpost list page
This commit is contained in:
parent
5d460a2537
commit
820f658b49
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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