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 guardian.mixins import PermissionRequiredMixin
|
||||
|
||||
from authentik.admin.views.utils import (
|
||||
BackSuccessUrlMixin,
|
||||
DeleteMessageView,
|
||||
)
|
||||
from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView
|
||||
from authentik.lib.views import CreateAssignPermView
|
||||
from authentik.outposts.forms import OutpostForm
|
||||
from authentik.outposts.models import Outpost, OutpostConfig
|
||||
|
|
|
@ -51,6 +51,13 @@ class OutpostViewSet(ModelViewSet):
|
|||
|
||||
queryset = Outpost.objects.all()
|
||||
serializer_class = OutpostSerializer
|
||||
filterset_fields = {
|
||||
"providers": ["isnull"],
|
||||
}
|
||||
search_fields = [
|
||||
"name",
|
||||
"providers__name",
|
||||
]
|
||||
|
||||
@swagger_auto_schema(responses={200: OutpostHealthSerializer(many=True)})
|
||||
@action(methods=["GET"], detail=True)
|
||||
|
|
31
swagger.yaml
31
swagger.yaml
|
@ -1789,6 +1789,11 @@ paths:
|
|||
operationId: outposts_outposts_list
|
||||
description: Outpost Viewset
|
||||
parameters:
|
||||
- name: providers__isnull
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
|
@ -1914,11 +1919,11 @@ paths:
|
|||
/outposts/outposts/{uuid}/health/:
|
||||
get:
|
||||
operationId: outposts_outposts_health
|
||||
description: Outpost Viewset
|
||||
description: Get outposts current health
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
description: Outpost health status
|
||||
schema:
|
||||
description: ''
|
||||
type: array
|
||||
|
@ -4457,7 +4462,7 @@ paths:
|
|||
/providers/oauth2/{id}/setup_urls/:
|
||||
get:
|
||||
operationId: providers_oauth2_setup_urls
|
||||
description: Return metadata as XML string
|
||||
description: Get Providers setup URLs
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
|
@ -8179,11 +8184,15 @@ definitions:
|
|||
type: string
|
||||
format: uuid
|
||||
x-nullable: true
|
||||
token_identifier:
|
||||
title: Token identifier
|
||||
type: string
|
||||
readOnly: true
|
||||
_config:
|
||||
title: config
|
||||
type: object
|
||||
OutpostHealth:
|
||||
description: ''
|
||||
description: Outpost health status
|
||||
type: object
|
||||
properties:
|
||||
last_seen:
|
||||
|
@ -8191,6 +8200,20 @@ definitions:
|
|||
type: string
|
||||
format: date-time
|
||||
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:
|
||||
title: 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})$`,
|
||||
),
|
||||
new SidebarItem("Providers", "/providers"),
|
||||
new SidebarItem("Outposts", "/administration/outposts/"),
|
||||
new SidebarItem("Outposts", "/outposts"),
|
||||
new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"),
|
||||
).when((): Promise<boolean> => {
|
||||
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/ProviderViewPage";
|
||||
import "./pages/property-mappings/PropertyMappingListPage";
|
||||
import "./pages/outposts/OutpostListPage";
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
// 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/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("^/outposts$"), html`<ak-outpost-list></ak-outpost-list>`),
|
||||
];
|
||||
|
|
Reference in New Issue