outposts: add better UI for showing mismatched versions (#10885)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -12,6 +12,7 @@ from rest_framework.views import APIView
|
|||||||
from authentik import __version__, get_build_hash
|
from authentik import __version__, get_build_hash
|
||||||
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version
|
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
|
from authentik.outposts.models import Outpost
|
||||||
|
|
||||||
|
|
||||||
class VersionSerializer(PassiveSerializer):
|
class VersionSerializer(PassiveSerializer):
|
||||||
@ -22,6 +23,7 @@ class VersionSerializer(PassiveSerializer):
|
|||||||
version_latest_valid = SerializerMethodField()
|
version_latest_valid = SerializerMethodField()
|
||||||
build_hash = SerializerMethodField()
|
build_hash = SerializerMethodField()
|
||||||
outdated = SerializerMethodField()
|
outdated = SerializerMethodField()
|
||||||
|
outpost_outdated = SerializerMethodField()
|
||||||
|
|
||||||
def get_build_hash(self, _) -> str:
|
def get_build_hash(self, _) -> str:
|
||||||
"""Get build hash, if version is not latest or released"""
|
"""Get build hash, if version is not latest or released"""
|
||||||
@ -47,6 +49,15 @@ class VersionSerializer(PassiveSerializer):
|
|||||||
"""Check if we're running the latest version"""
|
"""Check if we're running the latest version"""
|
||||||
return parse(self.get_version_current(instance)) < parse(self.get_version_latest(instance))
|
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):
|
class VersionView(APIView):
|
||||||
"""Get running and latest version."""
|
"""Get running and latest version."""
|
||||||
|
|||||||
@ -26,7 +26,6 @@ from authentik.outposts.apps import MANAGED_OUTPOST, MANAGED_OUTPOST_NAME
|
|||||||
from authentik.outposts.models import (
|
from authentik.outposts.models import (
|
||||||
Outpost,
|
Outpost,
|
||||||
OutpostConfig,
|
OutpostConfig,
|
||||||
OutpostState,
|
|
||||||
OutpostType,
|
OutpostType,
|
||||||
default_outpost_config,
|
default_outpost_config,
|
||||||
)
|
)
|
||||||
@ -182,7 +181,6 @@ class OutpostViewSet(UsedByMixin, ModelViewSet):
|
|||||||
outpost: Outpost = self.get_object()
|
outpost: Outpost = self.get_object()
|
||||||
states = []
|
states = []
|
||||||
for state in outpost.state:
|
for state in outpost.state:
|
||||||
state: OutpostState
|
|
||||||
states.append(
|
states.append(
|
||||||
{
|
{
|
||||||
"uid": state.uid,
|
"uid": state.uid,
|
||||||
|
|||||||
@ -451,7 +451,7 @@ class OutpostState:
|
|||||||
return False
|
return False
|
||||||
if self.build_hash != get_build_hash():
|
if self.build_hash != get_build_hash():
|
||||||
return False
|
return False
|
||||||
return parse(self.version) < OUR_VERSION
|
return parse(self.version) != OUR_VERSION
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def for_outpost(outpost: Outpost) -> list["OutpostState"]:
|
def for_outpost(outpost: Outpost) -> list["OutpostState"]:
|
||||||
|
|||||||
@ -36,7 +36,7 @@ def update_score(request: HttpRequest, identifier: str, amount: int):
|
|||||||
if not created:
|
if not created:
|
||||||
reputation.score = F("score") + amount
|
reputation.score = F("score") + amount
|
||||||
reputation.save()
|
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)
|
@receiver(login_failed)
|
||||||
|
|||||||
@ -187,7 +187,7 @@ func (a *APIController) OnRefresh() error {
|
|||||||
func (a *APIController) getWebsocketPingArgs() map[string]interface{} {
|
func (a *APIController) getWebsocketPingArgs() map[string]interface{} {
|
||||||
args := map[string]interface{}{
|
args := map[string]interface{}{
|
||||||
"version": constants.VERSION,
|
"version": constants.VERSION,
|
||||||
"buildHash": constants.BUILD("tagged"),
|
"buildHash": constants.BUILD(""),
|
||||||
"uuid": a.instanceUUID.String(),
|
"uuid": a.instanceUUID.String(),
|
||||||
"golangVersion": runtime.Version(),
|
"golangVersion": runtime.Version(),
|
||||||
"opensslEnabled": cryptobackend.OpensslEnabled,
|
"opensslEnabled": cryptobackend.OpensslEnabled,
|
||||||
@ -207,7 +207,7 @@ func (a *APIController) StartBackgroundTasks() error {
|
|||||||
"outpost_type": a.Server.Type(),
|
"outpost_type": a.Server.Type(),
|
||||||
"uuid": a.instanceUUID.String(),
|
"uuid": a.instanceUUID.String(),
|
||||||
"version": constants.VERSION,
|
"version": constants.VERSION,
|
||||||
"build": constants.BUILD("tagged"),
|
"build": constants.BUILD(""),
|
||||||
}).Set(1)
|
}).Set(1)
|
||||||
go func() {
|
go func() {
|
||||||
a.logger.Debug("Starting WS Handler...")
|
a.logger.Debug("Starting WS Handler...")
|
||||||
|
|||||||
@ -145,7 +145,7 @@ func (ac *APIController) startWSHandler() {
|
|||||||
"outpost_type": ac.Server.Type(),
|
"outpost_type": ac.Server.Type(),
|
||||||
"uuid": ac.instanceUUID.String(),
|
"uuid": ac.instanceUUID.String(),
|
||||||
"version": constants.VERSION,
|
"version": constants.VERSION,
|
||||||
"build": constants.BUILD("tagged"),
|
"build": constants.BUILD(""),
|
||||||
}).SetToCurrentTime()
|
}).SetToCurrentTime()
|
||||||
}
|
}
|
||||||
} else if wsMsg.Instruction == WebsocketInstructionProviderSpecific {
|
} else if wsMsg.Instruction == WebsocketInstructionProviderSpecific {
|
||||||
@ -207,7 +207,7 @@ func (ac *APIController) startIntervalUpdater() {
|
|||||||
"outpost_type": ac.Server.Type(),
|
"outpost_type": ac.Server.Type(),
|
||||||
"uuid": ac.instanceUUID.String(),
|
"uuid": ac.instanceUUID.String(),
|
||||||
"version": constants.VERSION,
|
"version": constants.VERSION,
|
||||||
"build": constants.BUILD("tagged"),
|
"build": constants.BUILD(""),
|
||||||
}).SetToCurrentTime()
|
}).SetToCurrentTime()
|
||||||
}
|
}
|
||||||
ticker.Reset(getInterval())
|
ticker.Reset(getInterval())
|
||||||
|
|||||||
@ -52712,9 +52712,14 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
description: Check if we're running the latest version
|
description: Check if we're running the latest version
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
outpost_outdated:
|
||||||
|
type: boolean
|
||||||
|
description: Check if any outpost is outdated/has a version mismatch
|
||||||
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
- build_hash
|
- build_hash
|
||||||
- outdated
|
- outdated
|
||||||
|
- outpost_outdated
|
||||||
- version_current
|
- version_current
|
||||||
- version_latest
|
- version_latest
|
||||||
- version_latest_valid
|
- version_latest_valid
|
||||||
|
|||||||
@ -31,6 +31,13 @@ export class VersionStatusCard extends AdminStatusCard<Version> {
|
|||||||
message: html`${msg(str`${value.versionLatest} is available!`)}`,
|
message: html`${msg(str`${value.versionLatest} is available!`)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (value.outpostOutdated) {
|
||||||
|
return Promise.resolve<AdminStatus>({
|
||||||
|
icon: "fa fa-exclamation-triangle pf-m-warning",
|
||||||
|
message: html`${msg("An outpost is on an incorrect version!")}
|
||||||
|
<a href="#/outpost/outposts">${msg("Check outposts.")}</a>`,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (value.versionLatestValid) {
|
if (value.versionLatestValid) {
|
||||||
return Promise.resolve<AdminStatus>({
|
return Promise.resolve<AdminStatus>({
|
||||||
icon: "fa fa-check-circle pf-m-success",
|
icon: "fa fa-check-circle pf-m-success",
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import { PFColor } from "@goauthentik/elements/Label";
|
import { PFColor } from "@goauthentik/elements/Label";
|
||||||
import "@goauthentik/elements/Spinner";
|
import "@goauthentik/elements/Spinner";
|
||||||
@ -49,7 +50,9 @@ export class OutpostHealthElement extends AKElement {
|
|||||||
<dd class="pf-c-description-list__description">
|
<dd class="pf-c-description-list__description">
|
||||||
<div class="pf-c-description-list__text">
|
<div class="pf-c-description-list__text">
|
||||||
<ak-label color=${PFColor.Green} ?compact=${true}>
|
<ak-label color=${PFColor.Green} ?compact=${true}>
|
||||||
${this.outpostHealth.lastSeen?.toLocaleTimeString()}
|
${msg(
|
||||||
|
str`${getRelativeTime(this.outpostHealth.lastSeen)} (${this.outpostHealth.lastSeen?.toLocaleTimeString()})`,
|
||||||
|
)}
|
||||||
</ak-label>
|
</ak-label>
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
|
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import { PFColor } from "@goauthentik/elements/Label";
|
import { PFColor } from "@goauthentik/elements/Label";
|
||||||
import "@goauthentik/elements/Spinner";
|
import "@goauthentik/elements/Spinner";
|
||||||
|
|
||||||
import { msg, str } from "@lit/localize";
|
import { msg, str } from "@lit/localize";
|
||||||
import { CSSResult, TemplateResult, html } from "lit";
|
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";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
@ -17,8 +18,8 @@ export class OutpostHealthSimpleElement extends AKElement {
|
|||||||
@property()
|
@property()
|
||||||
outpostId?: string;
|
outpostId?: string;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@state()
|
||||||
outpostHealth?: OutpostHealth;
|
outpostHealths: OutpostHealth[] = [];
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
loaded = false;
|
loaded = false;
|
||||||
@ -33,7 +34,7 @@ export class OutpostHealthSimpleElement extends AKElement {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
window.addEventListener(EVENT_REFRESH, () => {
|
window.addEventListener(EVENT_REFRESH, () => {
|
||||||
this.outpostHealth = undefined;
|
this.outpostHealths = [];
|
||||||
this.firstUpdated();
|
this.firstUpdated();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -46,9 +47,7 @@ export class OutpostHealthSimpleElement extends AKElement {
|
|||||||
})
|
})
|
||||||
.then((health) => {
|
.then((health) => {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
if (health.length >= 1) {
|
this.outpostHealths = health;
|
||||||
this.outpostHealth = health[0];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,11 +55,22 @@ export class OutpostHealthSimpleElement extends AKElement {
|
|||||||
if (!this.outpostId || !this.loaded) {
|
if (!this.outpostId || !this.loaded) {
|
||||||
return html`<ak-spinner></ak-spinner>`;
|
return html`<ak-spinner></ak-spinner>`;
|
||||||
}
|
}
|
||||||
if (!this.outpostHealth) {
|
if (!this.outpostHealths || this.outpostHealths.length === 0) {
|
||||||
return html`<ak-label color=${PFColor.Grey}>${msg("Not available")}</ak-label>`;
|
return html`<ak-label color=${PFColor.Grey}>${msg("Not available")}</ak-label>`;
|
||||||
}
|
}
|
||||||
|
const outdatedOutposts = this.outpostHealths.filter((h) => h.versionOutdated);
|
||||||
|
if (outdatedOutposts.length > 0) {
|
||||||
|
return html`<ak-label color=${PFColor.Red}>
|
||||||
|
${msg(
|
||||||
|
str`${outdatedOutposts[0].version}, should be ${outdatedOutposts[0].versionShould}`,
|
||||||
|
)}</ak-label
|
||||||
|
>`;
|
||||||
|
}
|
||||||
|
const lastSeen = this.outpostHealths[0].lastSeen;
|
||||||
return html`<ak-label color=${PFColor.Green}>
|
return html`<ak-label color=${PFColor.Green}>
|
||||||
${msg(str`Last seen: ${this.outpostHealth.lastSeen?.toLocaleTimeString()}`)}</ak-label
|
${msg(
|
||||||
|
str`Last seen: ${getRelativeTime(lastSeen)} (${lastSeen.toLocaleTimeString()})`,
|
||||||
|
)}</ak-label
|
||||||
>`;
|
>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export class OutpostListPage extends TablePage<Outpost> {
|
|||||||
const outposts = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesList(
|
const outposts = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesList(
|
||||||
await this.defaultEndpointConfig(),
|
await this.defaultEndpointConfig(),
|
||||||
);
|
);
|
||||||
Promise.all(
|
await Promise.all(
|
||||||
outposts.results.map((outpost) => {
|
outposts.results.map((outpost) => {
|
||||||
return new OutpostsApi(DEFAULT_CONFIG)
|
return new OutpostsApi(DEFAULT_CONFIG)
|
||||||
.outpostsInstancesHealthList({
|
.outpostsInstancesHealthList({
|
||||||
|
|||||||
Reference in New Issue
Block a user