diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py index 742e526db..93c184d80 100644 --- a/authentik/outposts/api/outposts.py +++ b/authentik/outposts/api/outposts.py @@ -1,6 +1,8 @@ """Outpost API Views""" from dacite.core import from_dict from dacite.exceptions import DaciteError +from django_filters.filters import ModelMultipleChoiceFilter +from django_filters.filterset import FilterSet from drf_spectacular.utils import extend_schema from rest_framework.decorators import action from rest_framework.fields import BooleanField, CharField, DateTimeField @@ -99,16 +101,30 @@ class OutpostHealthSerializer(PassiveSerializer): version_outdated = BooleanField(read_only=True) +class OutpostFilter(FilterSet): + """Filter for Outposts""" + + providers_by_pk = ModelMultipleChoiceFilter( + field_name="providers", + queryset=Provider.objects.all(), + ) + + class Meta: + + model = Outpost + fields = { + "providers": ["isnull"], + "name": ["iexact", "icontains"], + "service_connection__name": ["iexact", "icontains"], + } + + class OutpostViewSet(UsedByMixin, ModelViewSet): """Outpost Viewset""" queryset = Outpost.objects.all() serializer_class = OutpostSerializer - filterset_fields = { - "providers": ["isnull"], - "name": ["iexact", "icontains"], - "service_connection__name": ["iexact", "icontains"], - } + filterset_class = OutpostFilter search_fields = [ "name", "providers__name", diff --git a/schema.yml b/schema.yml index b95863b12..a0899416e 100644 --- a/schema.yml +++ b/schema.yml @@ -5764,6 +5764,14 @@ paths: name: providers__isnull schema: type: boolean + - in: query + name: providers_by_pk + schema: + type: array + items: + type: integer + explode: true + style: form - name: search required: false in: query @@ -5952,6 +5960,14 @@ paths: name: providers__isnull schema: type: boolean + - in: query + name: providers_by_pk + schema: + type: array + items: + type: integer + explode: true + style: form - name: search required: false in: query diff --git a/web/src/locales/en.po b/web/src/locales/en.po index f76436c1d..c71cb3398 100644 --- a/web/src/locales/en.po +++ b/web/src/locales/en.po @@ -5619,6 +5619,10 @@ msgstr "Wait (min)" msgid "Warning" msgstr "Warning" +#: src/pages/applications/ApplicationViewPage.ts +msgid "Warning: Application is not used by any Outpost." +msgstr "Warning: Application is not used by any Outpost." + #: src/pages/stages/invitation/InvitationListPage.ts msgid "Warning: No invitation stage is bound to any flow. Invitations will not work as expected." msgstr "Warning: No invitation stage is bound to any flow. Invitations will not work as expected." diff --git a/web/src/locales/fr_FR.po b/web/src/locales/fr_FR.po index caccfeeab..2c8e6c5f2 100644 --- a/web/src/locales/fr_FR.po +++ b/web/src/locales/fr_FR.po @@ -5557,6 +5557,10 @@ msgstr "Attente (min)" msgid "Warning" msgstr "Avertissement" +#: src/pages/applications/ApplicationViewPage.ts +msgid "Warning: Application is not used by any Outpost." +msgstr "" + #: src/pages/stages/invitation/InvitationListPage.ts msgid "Warning: No invitation stage is bound to any flow. Invitations will not work as expected." msgstr "" diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po index 20cb14f2f..291fc2387 100644 --- a/web/src/locales/pseudo-LOCALE.po +++ b/web/src/locales/pseudo-LOCALE.po @@ -5599,6 +5599,10 @@ msgstr "" msgid "Warning" msgstr "" +#: src/pages/applications/ApplicationViewPage.ts +msgid "Warning: Application is not used by any Outpost." +msgstr "" + #: src/pages/stages/invitation/InvitationListPage.ts msgid "Warning: No invitation stage is bound to any flow. Invitations will not work as expected." msgstr "" diff --git a/web/src/pages/applications/ApplicationViewPage.ts b/web/src/pages/applications/ApplicationViewPage.ts index cbd477a59..33ea89cf1 100644 --- a/web/src/pages/applications/ApplicationViewPage.ts +++ b/web/src/pages/applications/ApplicationViewPage.ts @@ -1,10 +1,11 @@ import { t } from "@lingui/macro"; import { CSSResult, LitElement, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import AKGlobal from "../../authentik.css"; +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; @@ -13,7 +14,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -import { Application, CoreApi } from "@goauthentik/api"; +import { Application, CoreApi, OutpostsApi } from "@goauthentik/api"; import { DEFAULT_CONFIG } from "../../api/Config"; import "../../elements/EmptyState"; @@ -36,14 +37,45 @@ export class ApplicationViewPage extends LitElement { }) .then((app) => { this.application = app; + if ( + app.providerObj && + [ + "authentik_providers_proxy.proxyprovider", + "authentik_providers_ldap.ldapprovider", + ].includes(app.providerObj.metaModelName) + ) { + new OutpostsApi(DEFAULT_CONFIG) + .outpostsInstancesList({ + providersByPk: [app.provider || 0], + pageSize: 1, + }) + .then((outposts) => { + if (outposts.pagination.count < 1) { + this.missingOutpost = true; + } + }); + } }); } @property({ attribute: false }) application!: Application; + @state() + missingOutpost = false; + static get styles(): CSSResult[] { - return [PFBase, PFPage, PFContent, PFButton, PFDescriptionList, PFGrid, PFCard, AKGlobal]; + return [ + PFBase, + PFBanner, + PFPage, + PFContent, + PFButton, + PFDescriptionList, + PFGrid, + PFCard, + AKGlobal, + ]; } render(): TemplateResult { @@ -61,7 +93,12 @@ export class ApplicationViewPage extends LitElement { if (!this.application) { return html` `; } - return html` + return html` + ${this.missingOutpost + ? html` + ${t`Warning: Application is not used by any Outpost.`} + ` + : html``} ${this.application.providerObj?.name} + (${this.application.providerObj?.verboseName})