diff --git a/passbook/admin/templates/administration/outpost/list.html b/passbook/admin/templates/administration/outpost/list.html index 2821164d4..3ce0492a7 100644 --- a/passbook/admin/templates/administration/outpost/list.html +++ b/passbook/admin/templates/administration/outpost/list.html @@ -44,6 +44,7 @@ {% trans 'Name' %} {% trans 'Providers' %} {% trans 'Health' %} + {% trans 'Version' %} @@ -59,7 +60,7 @@ - {% with health=outpost.health %} + {% with health=outpost.deployment_health %} {% if health %} {{ health|naturaltime }} {% else %} @@ -67,6 +68,17 @@ {% endif %} {% endwith %} + + + {% with ver=outpost.deployment_version %} + {% if ver.outdated or ver.version == "" %} + {{ ver.version|default:"-" }} + {% else %} + {{ ver.version }} + {% endif %} + {% endwith %} + + {% trans 'Edit' %} {% trans 'Delete' %} diff --git a/passbook/outposts/channels.py b/passbook/outposts/channels.py index 4a24da068..8d92bcbbf 100644 --- a/passbook/outposts/channels.py +++ b/passbook/outposts/channels.py @@ -83,7 +83,11 @@ class OutpostConsumer(JsonWebsocketConsumer): def receive_json(self, content: Data): msg = from_dict(WebsocketMessage, content) if msg.instruction == WebsocketMessageInstruction.HELLO: - cache.set(self.outpost.health_cache_key, time(), timeout=60) + cache.set(self.outpost.state_cache_prefix("health"), time(), timeout=60) + if "version" in msg.args: + cache.set( + self.outpost.state_cache_prefix("version"), msg.args["version"] + ) elif msg.instruction == WebsocketMessageInstruction.ACK: return diff --git a/passbook/outposts/models.py b/passbook/outposts/models.py index 147ed432c..8723bbd0d 100644 --- a/passbook/outposts/models.py +++ b/passbook/outposts/models.py @@ -1,7 +1,7 @@ """Outpost models""" from dataclasses import asdict, dataclass from datetime import datetime -from typing import Iterable, Optional +from typing import Any, Dict, Iterable, Optional from uuid import uuid4 from dacite import from_dict @@ -10,14 +10,19 @@ from django.core.cache import cache from django.db import models, transaction from django.db.models.base import Model from django.http import HttpRequest +from django.utils import version from django.utils.translation import gettext_lazy as _ from guardian.models import UserObjectPermission from guardian.shortcuts import assign_perm +from packaging.version import InvalidVersion, parse +from passbook import __version__ from passbook.core.models import Provider, Token, TokenIntents, User from passbook.lib.config import CONFIG from passbook.lib.utils.template import render_to_string +OUR_VERSION = parse(__version__) + @dataclass class OutpostConfig: @@ -93,20 +98,33 @@ class Outpost(models.Model): """Dump config into json""" self._config = asdict(value) - @property - def health_cache_key(self) -> str: - """Key by which the outposts health status is saved""" - return f"outpost_{self.uuid.hex}_health" + def state_cache_prefix(self, suffix: str) -> str: + """Key by which the outposts status is saved""" + return f"outpost_{self.uuid.hex}_state_{suffix}" @property - def health(self) -> Optional[datetime]: + def deployment_health(self) -> Optional[datetime]: """Get outpost's health status""" - key = self.health_cache_key + key = self.state_cache_prefix("health") value = cache.get(key, None) if value: return datetime.fromtimestamp(value) return None + @property + def deployment_version(self) -> Dict[str, Any]: + """Get deployed outposts version, and if the version is behind ours. + Returns a dict with keys version and outdated.""" + key = self.state_cache_prefix("version") + value = cache.get(key, None) + if not value: + return {"version": "", "outdated": False} + try: + outpost_version = parse(value) + return {"version": value, "outdated": outpost_version < OUR_VERSION} + except InvalidVersion: + return {"version": version, "outdated": False} + @property def user(self) -> User: """Get/create user with access to all required objects"""