diff --git a/authentik/admin/api/version.py b/authentik/admin/api/version.py index 3985bef9b028..72ddfa9eee27 100644 --- a/authentik/admin/api/version.py +++ b/authentik/admin/api/version.py @@ -12,6 +12,7 @@ from authentik import __version__, get_build_hash from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version from authentik.core.api.utils import PassiveSerializer +from authentik.outposts.models import Outpost class VersionSerializer(PassiveSerializer): @@ -22,6 +23,7 @@ class VersionSerializer(PassiveSerializer): version_latest_valid = SerializerMethodField() build_hash = SerializerMethodField() outdated = SerializerMethodField() + outpost_outdated = SerializerMethodField() def get_build_hash(self, _) -> str: """Get build hash, if version is not latest or released""" @@ -47,6 +49,15 @@ 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 get_outpost_outdated(self, _) -> bool: + """Check if any outpost is outdated/has a version mismatch""" + any_outdated = False + for outpost in Outpost.objects.all(): + for state in outpost.state: + if state.version_outdated: + any_outdated = True + return any_outdated + class VersionView(APIView): """Get running and latest version.""" diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py index 582dd1eac335..ba84cf42e337 100644 --- a/authentik/outposts/api/outposts.py +++ b/authentik/outposts/api/outposts.py @@ -26,7 +26,6 @@ from authentik.outposts.models import ( Outpost, OutpostConfig, - OutpostState, OutpostType, default_outpost_config, ) @@ -182,7 +181,6 @@ def health(self, request: Request, pk: int) -> Response: outpost: Outpost = self.get_object() states = [] for state in outpost.state: - state: OutpostState states.append( { "uid": state.uid, diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index 327d23ca4698..2a6f45789fb6 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -451,7 +451,7 @@ def version_outdated(self) -> bool: return False if self.build_hash != get_build_hash(): return False - return parse(self.version) < OUR_VERSION + return parse(self.version) != OUR_VERSION @staticmethod def for_outpost(outpost: Outpost) -> list["OutpostState"]: diff --git a/authentik/policies/reputation/signals.py b/authentik/policies/reputation/signals.py index a3969c4d9e54..2092e3512ded 100644 --- a/authentik/policies/reputation/signals.py +++ b/authentik/policies/reputation/signals.py @@ -36,7 +36,7 @@ def update_score(request: HttpRequest, identifier: str, amount: int): if not created: reputation.score = F("score") + amount reputation.save() - LOGGER.debug("Updated score", amount=amount, for_user=identifier, for_ip=remote_ip) + LOGGER.info("Updated score", amount=amount, for_user=identifier, for_ip=remote_ip) @receiver(login_failed) diff --git a/internal/outpost/ak/api.go b/internal/outpost/ak/api.go index fca9a6fb9c7a..57757b2e2a6e 100644 --- a/internal/outpost/ak/api.go +++ b/internal/outpost/ak/api.go @@ -187,7 +187,7 @@ func (a *APIController) OnRefresh() error { func (a *APIController) getWebsocketPingArgs() map[string]interface{} { args := map[string]interface{}{ "version": constants.VERSION, - "buildHash": constants.BUILD("tagged"), + "buildHash": constants.BUILD(""), "uuid": a.instanceUUID.String(), "golangVersion": runtime.Version(), "opensslEnabled": cryptobackend.OpensslEnabled, @@ -207,7 +207,7 @@ func (a *APIController) StartBackgroundTasks() error { "outpost_type": a.Server.Type(), "uuid": a.instanceUUID.String(), "version": constants.VERSION, - "build": constants.BUILD("tagged"), + "build": constants.BUILD(""), }).Set(1) go func() { a.logger.Debug("Starting WS Handler...") diff --git a/internal/outpost/ak/api_ws.go b/internal/outpost/ak/api_ws.go index 210b7e5335ce..cda7bd03d2b0 100644 --- a/internal/outpost/ak/api_ws.go +++ b/internal/outpost/ak/api_ws.go @@ -145,7 +145,7 @@ func (ac *APIController) startWSHandler() { "outpost_type": ac.Server.Type(), "uuid": ac.instanceUUID.String(), "version": constants.VERSION, - "build": constants.BUILD("tagged"), + "build": constants.BUILD(""), }).SetToCurrentTime() } } else if wsMsg.Instruction == WebsocketInstructionProviderSpecific { @@ -207,7 +207,7 @@ func (ac *APIController) startIntervalUpdater() { "outpost_type": ac.Server.Type(), "uuid": ac.instanceUUID.String(), "version": constants.VERSION, - "build": constants.BUILD("tagged"), + "build": constants.BUILD(""), }).SetToCurrentTime() } ticker.Reset(getInterval()) diff --git a/schema.yml b/schema.yml index a0ff661ebb1b..bb6df8dd6275 100644 --- a/schema.yml +++ b/schema.yml @@ -52712,9 +52712,14 @@ components: type: boolean description: Check if we're running the latest version readOnly: true + outpost_outdated: + type: boolean + description: Check if any outpost is outdated/has a version mismatch + readOnly: true required: - build_hash - outdated + - outpost_outdated - version_current - version_latest - version_latest_valid diff --git a/web/src/admin/admin-overview/cards/VersionStatusCard.ts b/web/src/admin/admin-overview/cards/VersionStatusCard.ts index e241884726f5..804efd6cd624 100644 --- a/web/src/admin/admin-overview/cards/VersionStatusCard.ts +++ b/web/src/admin/admin-overview/cards/VersionStatusCard.ts @@ -31,6 +31,13 @@ export class VersionStatusCard extends AdminStatusCard { message: html`${msg(str`${value.versionLatest} is available!`)}`, }); } + if (value.outpostOutdated) { + return Promise.resolve({ + icon: "fa fa-exclamation-triangle pf-m-warning", + message: html`${msg("An outpost is on an incorrect version!")} + ${msg("Check outposts.")}`, + }); + } if (value.versionLatestValid) { return Promise.resolve({ icon: "fa fa-check-circle pf-m-success", diff --git a/web/src/admin/outposts/OutpostHealth.ts b/web/src/admin/outposts/OutpostHealth.ts index 57fc0833ac49..a8c63fc2835b 100644 --- a/web/src/admin/outposts/OutpostHealth.ts +++ b/web/src/admin/outposts/OutpostHealth.ts @@ -1,3 +1,4 @@ +import { getRelativeTime } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/Spinner"; @@ -49,7 +50,9 @@ export class OutpostHealthElement extends AKElement {
- ${this.outpostHealth.lastSeen?.toLocaleTimeString()} + ${msg( + str`${getRelativeTime(this.outpostHealth.lastSeen)} (${this.outpostHealth.lastSeen?.toLocaleTimeString()})`, + )}
diff --git a/web/src/admin/outposts/OutpostHealthSimple.ts b/web/src/admin/outposts/OutpostHealthSimple.ts index eac52f8eeda7..51f0061d8a60 100644 --- a/web/src/admin/outposts/OutpostHealthSimple.ts +++ b/web/src/admin/outposts/OutpostHealthSimple.ts @@ -1,12 +1,13 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; +import { getRelativeTime } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/Spinner"; import { msg, str } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; @@ -17,8 +18,8 @@ export class OutpostHealthSimpleElement extends AKElement { @property() outpostId?: string; - @property({ attribute: false }) - outpostHealth?: OutpostHealth; + @state() + outpostHealths: OutpostHealth[] = []; @property({ attribute: false }) loaded = false; @@ -33,7 +34,7 @@ export class OutpostHealthSimpleElement extends AKElement { constructor() { super(); window.addEventListener(EVENT_REFRESH, () => { - this.outpostHealth = undefined; + this.outpostHealths = []; this.firstUpdated(); }); } @@ -46,9 +47,7 @@ export class OutpostHealthSimpleElement extends AKElement { }) .then((health) => { this.loaded = true; - if (health.length >= 1) { - this.outpostHealth = health[0]; - } + this.outpostHealths = health; }); } @@ -56,11 +55,22 @@ export class OutpostHealthSimpleElement extends AKElement { if (!this.outpostId || !this.loaded) { return html``; } - if (!this.outpostHealth) { + if (!this.outpostHealths || this.outpostHealths.length === 0) { return html`${msg("Not available")}`; } + const outdatedOutposts = this.outpostHealths.filter((h) => h.versionOutdated); + if (outdatedOutposts.length > 0) { + return html` + ${msg( + str`${outdatedOutposts[0].version}, should be ${outdatedOutposts[0].versionShould}`, + )}`; + } + const lastSeen = this.outpostHealths[0].lastSeen; return html` - ${msg(str`Last seen: ${this.outpostHealth.lastSeen?.toLocaleTimeString()}`)}`; } } diff --git a/web/src/admin/outposts/OutpostListPage.ts b/web/src/admin/outposts/OutpostListPage.ts index 02aa01ea6fb4..7784c11574e9 100644 --- a/web/src/admin/outposts/OutpostListPage.ts +++ b/web/src/admin/outposts/OutpostListPage.ts @@ -70,7 +70,7 @@ export class OutpostListPage extends TablePage { const outposts = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesList( await this.defaultEndpointConfig(), ); - Promise.all( + await Promise.all( outposts.results.map((outpost) => { return new OutpostsApi(DEFAULT_CONFIG) .outpostsInstancesHealthList({