mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-03 10:36:45 +09:00
~60 call sites across 24 model files now ride _json_read with each site's exact default preserved; 34 classes newly adopt the mixin (registry contract test expanded to 36). Reverses the old 'bare sites stay raising' ruling: a corrupt row degrades to its default instead of 500ing every serializing endpoint (docstring updated). Ratchet: json.loads( appears only in json_column_mixin.py. Known pre-existing: test_host_inventory enumerate_filesystems failures on this machine (verified via clean-tree stash rerun). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
83 lines
3.3 KiB
Python
83 lines
3.3 KiB
Python
"""The panel host's own hardware, recorded so a change can be noticed.
|
|
|
|
Every other spec reader in the codebase answers "what is it now?" — psutil is
|
|
consulted live and the answer is thrown away. That is fine until the operator
|
|
resizes the box, at which point the panel reads the new numbers, has nothing to
|
|
compare them against, and says nothing.
|
|
|
|
The subtle part is that a resize requires a power-off, so the process (and any
|
|
in-memory cache) dies with it. The baseline has to outlive a reboot or it is
|
|
not a baseline, which is why this is a table and not a module-level dict.
|
|
"""
|
|
import json
|
|
from datetime import datetime
|
|
|
|
from app import db
|
|
from app.models.json_column_mixin import JsonColumnMixin
|
|
|
|
|
|
class HostSnapshot(JsonColumnMixin, db.Model):
|
|
"""One reading of the panel host's specs and filesystems."""
|
|
__tablename__ = 'host_snapshots'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
captured_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
|
|
|
|
# /proc/sys/kernel/random/boot_id — a fresh value every boot. Lets a change
|
|
# that happened while the box was off (a resize) be told apart from one that
|
|
# happened underneath a running panel (a volume attached hot).
|
|
boot_id = db.Column(db.String(64), nullable=True, index=True)
|
|
|
|
cpu_cores = db.Column(db.Integer, nullable=True)
|
|
ram_bytes = db.Column(db.BigInteger, nullable=True)
|
|
swap_bytes = db.Column(db.BigInteger, nullable=True)
|
|
container = db.Column(db.String(32), nullable=True) # lxc | openvz | docker | None
|
|
|
|
# [{device, mountpoint, fstype, total, used, free, percent,
|
|
# in_fstab, is_data_volume}, ...]
|
|
filesystems_json = db.Column(db.Text, nullable=True)
|
|
|
|
# Deltas against the previous snapshot: [{field, kind, from, to, summary}].
|
|
# NULL on the first ever capture — there was nothing to compare against,
|
|
# which is different from "compared and found nothing".
|
|
changes_json = db.Column(db.Text, nullable=True)
|
|
|
|
# Advisories active at capture time, so the next capture can notify only on
|
|
# a state transition instead of nagging every boot.
|
|
advisories_json = db.Column(db.Text, nullable=True)
|
|
|
|
def get_filesystems(self):
|
|
return self._json_read('filesystems_json', [])
|
|
|
|
def set_filesystems(self, value):
|
|
self.filesystems_json = json.dumps(value) if value else None
|
|
|
|
def get_changes(self):
|
|
"""Deltas vs the previous snapshot, or None if this is the first."""
|
|
if self.changes_json is None:
|
|
return None
|
|
return self._json_read('changes_json', [])
|
|
|
|
def set_changes(self, value):
|
|
self.changes_json = json.dumps(value) if value is not None else None
|
|
|
|
def get_advisories(self):
|
|
return self._json_read('advisories_json', [])
|
|
|
|
def set_advisories(self, value):
|
|
self.advisories_json = json.dumps(value) if value else None
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'captured_at': self.captured_at.isoformat() if self.captured_at else None,
|
|
'boot_id': self.boot_id,
|
|
'cpu_cores': self.cpu_cores,
|
|
'ram_bytes': self.ram_bytes,
|
|
'swap_bytes': self.swap_bytes,
|
|
'container': self.container,
|
|
'filesystems': self.get_filesystems(),
|
|
'changes': self.get_changes(),
|
|
'advisories': self.get_advisories(),
|
|
}
|