ProxMenux 1.2.6.2-beta: OCI containers in the Monitor, docs and fixes

OCI manager Apps
- App tab: containers installed from an OCI image are identified from their
  installation record; the application and image versions are shown and an
  update is detected by image digest; repository link; Refresh data.
- Updates tab for OCI containers: Update and Recreate run the same flow as the
  OCI menu in the Monitor terminal; the pre-update backup can be kept in a
  backup storage; scheduled image updates with an optional minimum age.
- Logs tab: console output of the application, kept on the host
  (lxc.console.logfile + logrotate) and followed live.
- The Proxmox console opens a shell (cmode: shell) when the image has one.
- A damaged image download is fetched again before failing.
- Multi-container applications open at their LAN address; volume mount
  points on block storage report their usage.

Monitor
- Proxmox notifications are delivered to a loopback-only HTTP listener when
  HTTPS is enabled, so they no longer fail certificate verification.
- Log persistence counts recurring patterns only; an ended burst is not
  reported as persistent and its warning clears on its own (#386).
- Proxmox notification config backups are deduplicated and capped at three.
- The update icon on the Apps page opens the container on its Updates tab.
- Version 1.2.6.2-beta and its release notes in every Monitor language.

Docs
- OCI manager Apps and Audit & Report rebuilt as per-page message files,
  with a new page for OCI containers in the Monitor.
- Seven pages fixed where rich-text tags were missing from t.rich.

Translations
- Spanish fixes across the OCI engine, the Monitor and the TUI menus.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-09-25 21:51:12 +02:00
co-authored by Claude Opus 5.5
parent 386d33df6e
commit 4437a671d2
524 changed files with 14459 additions and 3841 deletions
+33 -8
View File
@@ -116,7 +116,9 @@ def protect_placeholders(text: str) -> tuple[str, list[str]]:
def _swap(match: re.Match) -> str:
placeholders.append(match.group(0))
return f"__PMX_PH_{len(placeholders) - 1}__"
# No underscores: a local model splits on them and returns
# "PMX PH 0", so the placeholder never comes back.
return f"PMXPH{len(placeholders) - 1:03d}"
return PLACEHOLDER_RE.sub(_swap, text), placeholders
@@ -127,7 +129,7 @@ def restore_placeholders(text: str, placeholders: list[str]) -> str:
fallback assignment (target = existing or EN) upstream catches
the worst case."""
for i, original in enumerate(placeholders):
text = text.replace(f"__PMX_PH_{i}__", original)
text = text.replace(f"PMXPH{i:03d}", original)
return text
@@ -241,9 +243,16 @@ def build_arg_parser() -> argparse.ArgumentParser:
"--refresh",
action="store_true",
help=(
"Re-translate EVERY key, ignoring existing translations. "
"Dangerous: this DOES overwrite human-curated strings. "
"Use only when you know what you are doing."
"Retranslate the keys named by --refresh-keys. On its own it has "
"no effect: an existing translation is never replaced."
),
)
parser.add_argument(
"--refresh-keys",
type=Path,
help=(
"File with one dotted message key per line. Only these may be "
"retranslated over an existing value, and only with --refresh."
),
)
parser.add_argument(
@@ -283,6 +292,21 @@ def main() -> int:
print(f"Provider: {args.provider}", flush=True)
print(f"Sleep between calls: {args.sleep}s", flush=True)
refresh_keys: set[str] = set()
if args.refresh_keys is not None:
if not args.refresh:
print("--refresh-keys needs --refresh.", file=sys.stderr)
return 1
refresh_keys = {
line.strip()
for line in args.refresh_keys.read_text(encoding="utf-8").splitlines()
if line.strip()
}
print(f"Refreshing {len(refresh_keys)} named keys.", flush=True)
elif args.refresh:
print("--refresh without --refresh-keys: existing translations are kept.",
flush=True)
total_failures: list[tuple[str, CatalogPath, str]] = []
# Preflight every locale before any provider call or write. Serialized
@@ -331,8 +355,9 @@ def main() -> int:
# 3. Auto-fills from prior runs whose output happened to
# match EN — leaving them alone is the intended
# steady-state, not a bug.
# When someone genuinely wants to redo everything, --refresh
# is still available (and is destructive by design).
# An existing translation is never replaced. --refresh only reaches
# the keys named in --refresh-keys, so an overwrite is always one
# somebody asked for by name rather than a side effect of a run.
# Seed structural nodes and non-text leaves before any checkpoint.
# Only strings are sent to a translation provider.
original_flat = target_flat.copy()
@@ -348,7 +373,7 @@ def main() -> int:
if not isinstance(en_value, str) or not en_value:
continue
existing = target_flat.get(key, "")
if args.refresh or not existing:
if not existing or ".".join(str(part) for part in key) in refresh_keys:
missing.append(key)
if args.limit > 0: