printers low-toner alerts; geenforce display readiness + token scope-binding; air-gap docker deploy kit

This commit is contained in:
2026-07-23 14:37:00 -04:00
parent 73b05e5b1c
commit c355eaae43
32 changed files with 2358 additions and 46 deletions

View File

@@ -9,9 +9,11 @@ from flask import Flask, Blueprint
import click
from shopdb.plugins.base import BasePlugin, PluginMeta
from shopdb.api import db, AssetType
from shopdb.api import db, AssetType, Setting
from .models import Printer, PrinterType, ModelSupply, PrinterDriver
from .models import (
Printer, PrinterType, ModelSupply, PrinterDriver, PrinterSupplyAlert
)
from .api import printers_asset_bp
from .services import ZabbixService
@@ -76,6 +78,7 @@ class PrintersPlugin(BasePlugin):
PrinterType, # printer type classification
PrinterDriver, # driver links (SMB/HTTP)
ModelSupply, # model -> toner/drum/waste part numbers
PrinterSupplyAlert, # per-printer toner alert crossing state
]
def get_services(self) -> Dict[str, Type]:
@@ -103,8 +106,41 @@ class PrintersPlugin(BasePlugin):
with app.app_context():
self._ensure_asset_type()
self._ensure_printer_types()
self._seed_settings()
logger.info("Printers plugin installed")
def on_enable(self, app: Flask) -> None:
# Idempotent re-seed so settings added in later versions reach sites
# that installed earlier (enable runs on every upgrade cycle).
with app.app_context():
self._seed_settings()
def _seed_settings(self) -> None:
"""Seed low-toner alert recipient settings (idempotent)."""
defaults = [
('printers_alert_email', '', 'string',
'Comma-separated low-toner alert recipients; empty uses the '
'site alert_recipients'),
('printers_alert_userids', '', 'string',
'Comma-separated shopdb user ids whose account emails receive '
'low-toner alerts'),
('printers_alert_roleids', '', 'string',
'Comma-separated role ids; every active member of these roles '
'receives low-toner alerts'),
('printers_alert_supportteamid', '', 'string',
'Support team whose webhook receives low-toner alerts; empty '
'uses the site alert_webhook_url'),
('printers_alert_warning_threshold', '5', 'integer',
'Toner percent remaining at or below which a warning email fires'),
('printers_alert_critical_threshold', '0', 'integer',
'Toner percent remaining at or below which a critical email fires'),
]
for key, value, valuetype, description in defaults:
if Setting.get(key) is None:
Setting.set(key, value, valuetype=valuetype,
category='printers', description=description)
db.session.commit()
def _ensure_asset_type(self) -> None:
"""Ensure printer asset type exists."""
existing = AssetType.query.filter_by(assettype='printer').first()
@@ -151,6 +187,19 @@ class PrintersPlugin(BasePlugin):
"""Called when plugin is uninstalled."""
logger.info("Printers plugin uninstalled")
def get_settings_cards(self) -> List[dict]:
return [
{
'group': 'Printers',
'to': '/settings/printer-alerts',
'icon': 'bell',
'title': 'Low-Toner Alerts',
'description': 'Who gets warning (5%) and critical (0%) toner '
'emails, and which support team webhook',
'position': 48,
},
]
def get_cli_commands(self) -> List:
"""Return CLI commands for this plugin."""
@@ -159,6 +208,22 @@ class PrintersPlugin(BasePlugin):
"""Printers plugin commands."""
pass
@printerscli.command('check-toner-alerts')
def checktoneralerts():
"""Poll Zabbix for all printers and email/webhook low-toner crossings.
Run on a schedule (scheduled task / cron). Fires a warning at or
below 5 percent and a critical at 0 percent, once per crossing."""
from flask import current_app
from .services import check_supplies
with current_app.app_context():
summary = check_supplies()
click.echo(
f"Toner poll: {summary['polled']}/{summary['printers']} "
f"printers reachable, {summary['alerts']} alert(s) sent, "
f"{summary['rearmed']} re-armed.")
@printerscli.command('check-supplies')
@click.argument('ip')
def checksupplies(ip):