Initial commit: Shop Database Flask Application
Flask backend with Vue 3 frontend for shop floor machine management. Includes database schema export for MySQL shopdb_flask database. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
1
plugins/__init__.py
Normal file
1
plugins/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""ShopDB plugins package."""
|
||||
5
plugins/printers/__init__.py
Normal file
5
plugins/printers/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Printers plugin - extends machines with printer-specific functionality."""
|
||||
|
||||
from .plugin import PrintersPlugin
|
||||
|
||||
__all__ = ['PrintersPlugin']
|
||||
5
plugins/printers/api/__init__.py
Normal file
5
plugins/printers/api/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Printers plugin API."""
|
||||
|
||||
from .routes import printers_bp
|
||||
|
||||
__all__ = ['printers_bp']
|
||||
243
plugins/printers/api/routes.py
Normal file
243
plugins/printers/api/routes.py
Normal file
@@ -0,0 +1,243 @@
|
||||
"""Printers API routes."""
|
||||
|
||||
from flask import Blueprint, request
|
||||
from flask_jwt_extended import jwt_required
|
||||
|
||||
from shopdb.extensions import db
|
||||
from shopdb.utils.responses import success_response, error_response, paginated_response, ErrorCodes
|
||||
from shopdb.utils.pagination import get_pagination_params, paginate_query
|
||||
from shopdb.core.models.machine import Machine, MachineType
|
||||
from shopdb.core.models.communication import Communication, CommunicationType
|
||||
|
||||
from ..models import PrinterData
|
||||
from ..services import ZabbixService
|
||||
|
||||
printers_bp = Blueprint('printers', __name__)
|
||||
|
||||
|
||||
@printers_bp.route('/', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def list_printers():
|
||||
"""List all printers."""
|
||||
page, per_page = get_pagination_params(request)
|
||||
|
||||
# Get printer machine types
|
||||
printer_types = MachineType.query.filter_by(category='Printer').all()
|
||||
printer_type_ids = [pt.machinetypeid for pt in printer_types]
|
||||
|
||||
query = Machine.query.filter(
|
||||
Machine.machinetypeid.in_(printer_type_ids),
|
||||
Machine.isactive == True
|
||||
)
|
||||
|
||||
# Filters
|
||||
if location_id := request.args.get('location', type=int):
|
||||
query = query.filter(Machine.locationid == location_id)
|
||||
|
||||
if search := request.args.get('search'):
|
||||
query = query.filter(
|
||||
db.or_(
|
||||
Machine.machinenumber.ilike(f'%{search}%'),
|
||||
Machine.hostname.ilike(f'%{search}%'),
|
||||
Machine.alias.ilike(f'%{search}%')
|
||||
)
|
||||
)
|
||||
|
||||
query = query.order_by(Machine.machinenumber)
|
||||
items, total = paginate_query(query, page, per_page)
|
||||
|
||||
printers = []
|
||||
for machine in items:
|
||||
printer_data = {
|
||||
'machineid': machine.machineid,
|
||||
'machinenumber': machine.machinenumber,
|
||||
'hostname': machine.hostname,
|
||||
'alias': machine.alias,
|
||||
'serialnumber': machine.serialnumber,
|
||||
'location': machine.location.locationname if machine.location else None,
|
||||
'vendor': machine.vendor.vendor if machine.vendor else None,
|
||||
'model': machine.model.modelnumber if machine.model else None,
|
||||
'status': machine.status.status if machine.status else None,
|
||||
}
|
||||
|
||||
# Add printer-specific data
|
||||
if machine.printerdata:
|
||||
pd = machine.printerdata
|
||||
printer_data['printerdata'] = {
|
||||
'windowsname': pd.windowsname,
|
||||
'sharename': pd.sharename,
|
||||
'iscsf': pd.iscsf,
|
||||
'pin': pd.pin,
|
||||
}
|
||||
|
||||
# Get IP from communications
|
||||
primary_comm = next((c for c in machine.communications if c.isprimary), None)
|
||||
if not primary_comm and machine.communications:
|
||||
primary_comm = machine.communications[0]
|
||||
printer_data['ipaddress'] = primary_comm.ipaddress if primary_comm else None
|
||||
|
||||
printers.append(printer_data)
|
||||
|
||||
return paginated_response(printers, page, per_page, total)
|
||||
|
||||
|
||||
@printers_bp.route('/<int:machine_id>', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def get_printer(machine_id: int):
|
||||
"""Get a single printer with details."""
|
||||
machine = Machine.query.get(machine_id)
|
||||
|
||||
if not machine:
|
||||
return error_response(ErrorCodes.NOT_FOUND, 'Printer not found', http_code=404)
|
||||
|
||||
data = machine.to_dict()
|
||||
data['machinetype'] = machine.machinetype.to_dict() if machine.machinetype else None
|
||||
data['vendor'] = machine.vendor.to_dict() if machine.vendor else None
|
||||
data['model'] = machine.model.to_dict() if machine.model else None
|
||||
data['location'] = machine.location.to_dict() if machine.location else None
|
||||
data['status'] = machine.status.to_dict() if machine.status else None
|
||||
data['communications'] = [c.to_dict() for c in machine.communications]
|
||||
|
||||
# Add printer-specific data
|
||||
if machine.printerdata:
|
||||
pd = machine.printerdata
|
||||
data['printerdata'] = {
|
||||
'id': pd.id,
|
||||
'windowsname': pd.windowsname,
|
||||
'sharename': pd.sharename,
|
||||
'iscsf': pd.iscsf,
|
||||
'installpath': pd.installpath,
|
||||
'pin': pd.pin,
|
||||
}
|
||||
|
||||
return success_response(data)
|
||||
|
||||
|
||||
@printers_bp.route('/<int:machine_id>/printerdata', methods=['PUT'])
|
||||
@jwt_required()
|
||||
def update_printer_data(machine_id: int):
|
||||
"""Update printer-specific data."""
|
||||
machine = Machine.query.get(machine_id)
|
||||
|
||||
if not machine:
|
||||
return error_response(ErrorCodes.NOT_FOUND, 'Printer not found', http_code=404)
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'No data provided')
|
||||
|
||||
# Get or create printer data
|
||||
pd = machine.printerdata
|
||||
if not pd:
|
||||
pd = PrinterData(machineid=machine_id)
|
||||
db.session.add(pd)
|
||||
|
||||
for key in ['windowsname', 'sharename', 'iscsf', 'installpath', 'pin']:
|
||||
if key in data:
|
||||
setattr(pd, key, data[key])
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return success_response({
|
||||
'id': pd.id,
|
||||
'windowsname': pd.windowsname,
|
||||
'sharename': pd.sharename,
|
||||
'iscsf': pd.iscsf,
|
||||
'installpath': pd.installpath,
|
||||
'pin': pd.pin,
|
||||
}, message='Printer data updated')
|
||||
|
||||
|
||||
@printers_bp.route('/<int:machine_id>/communication', methods=['PUT'])
|
||||
@jwt_required()
|
||||
def update_printer_communication(machine_id: int):
|
||||
"""Update printer communication (IP address)."""
|
||||
machine = Machine.query.get(machine_id)
|
||||
|
||||
if not machine:
|
||||
return error_response(ErrorCodes.NOT_FOUND, 'Printer not found', http_code=404)
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'No data provided')
|
||||
|
||||
# Get or create IP communication type
|
||||
ip_comtype = CommunicationType.query.filter_by(comtype='IP').first()
|
||||
if not ip_comtype:
|
||||
ip_comtype = CommunicationType(comtype='IP', description='IP Network')
|
||||
db.session.add(ip_comtype)
|
||||
db.session.flush()
|
||||
|
||||
# Find existing primary communication or create new one
|
||||
comm = next((c for c in machine.communications if c.isprimary), None)
|
||||
if not comm:
|
||||
comm = next((c for c in machine.communications if c.comtypeid == ip_comtype.comtypeid), None)
|
||||
if not comm:
|
||||
comm = Communication(machineid=machine_id, comtypeid=ip_comtype.comtypeid)
|
||||
db.session.add(comm)
|
||||
|
||||
# Update fields
|
||||
if 'ipaddress' in data:
|
||||
comm.ipaddress = data['ipaddress']
|
||||
if 'isprimary' in data:
|
||||
comm.isprimary = data['isprimary']
|
||||
if 'macaddress' in data:
|
||||
comm.macaddress = data['macaddress']
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return success_response({
|
||||
'communicationid': comm.communicationid,
|
||||
'ipaddress': comm.ipaddress,
|
||||
'isprimary': comm.isprimary,
|
||||
}, message='Communication updated')
|
||||
|
||||
|
||||
@printers_bp.route('/<int:machine_id>/supplies', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def get_printer_supplies(machine_id: int):
|
||||
"""Get supply levels from Zabbix (real-time lookup)."""
|
||||
machine = Machine.query.get(machine_id)
|
||||
|
||||
if not machine:
|
||||
return error_response(ErrorCodes.NOT_FOUND, 'Printer not found', http_code=404)
|
||||
|
||||
# Get IP address
|
||||
primary_comm = next((c for c in machine.communications if c.isprimary), None)
|
||||
if not primary_comm and machine.communications:
|
||||
primary_comm = machine.communications[0]
|
||||
|
||||
if not primary_comm or not primary_comm.ipaddress:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'Printer has no IP address')
|
||||
|
||||
service = ZabbixService()
|
||||
if not service.isconfigured:
|
||||
return error_response(ErrorCodes.SERVICE_UNAVAILABLE, 'Zabbix not configured')
|
||||
|
||||
supplies = service.getsuppliesbyip(primary_comm.ipaddress)
|
||||
|
||||
return success_response({
|
||||
'ipaddress': primary_comm.ipaddress,
|
||||
'supplies': supplies or []
|
||||
})
|
||||
|
||||
|
||||
@printers_bp.route('/dashboard/summary', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def dashboard_summary():
|
||||
"""Get printer summary for dashboard."""
|
||||
printer_types = MachineType.query.filter_by(category='Printer').all()
|
||||
printer_type_ids = [pt.machinetypeid for pt in printer_types]
|
||||
|
||||
total = Machine.query.filter(
|
||||
Machine.machinetypeid.in_(printer_type_ids),
|
||||
Machine.isactive == True
|
||||
).count()
|
||||
|
||||
return success_response({
|
||||
'totalprinters': total,
|
||||
'total': total,
|
||||
'online': total, # Placeholder - would need Zabbix integration for real status
|
||||
'lowsupplies': 0,
|
||||
'criticalsupplies': 0
|
||||
})
|
||||
25
plugins/printers/manifest.json
Normal file
25
plugins/printers/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "printers",
|
||||
"version": "1.0.0",
|
||||
"description": "Printer management plugin with Zabbix integration, supply tracking, and QR codes",
|
||||
"author": "ShopDB Team",
|
||||
"dependencies": [],
|
||||
"core_version": ">=1.0.0",
|
||||
"api_prefix": "/api/printers",
|
||||
"provides": {
|
||||
"machine_category": "Printer",
|
||||
"features": [
|
||||
"printer_extensions",
|
||||
"driver_management",
|
||||
"supply_tracking",
|
||||
"zabbix_integration",
|
||||
"qr_codes"
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"zabbix_url": "",
|
||||
"zabbix_token": "",
|
||||
"supply_alert_threshold": 10,
|
||||
"default_driver_source": "internal"
|
||||
}
|
||||
}
|
||||
1
plugins/printers/migrations/__init__.py
Normal file
1
plugins/printers/migrations/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Printers plugin migrations."""
|
||||
7
plugins/printers/models/__init__.py
Normal file
7
plugins/printers/models/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Printers plugin models."""
|
||||
|
||||
from .printer_extension import PrinterData
|
||||
|
||||
__all__ = [
|
||||
'PrinterData',
|
||||
]
|
||||
58
plugins/printers/models/printer_extension.py
Normal file
58
plugins/printers/models/printer_extension.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""PrinterData model - printer-specific fields linked to machines."""
|
||||
|
||||
from shopdb.extensions import db
|
||||
from shopdb.core.models.base import BaseModel
|
||||
|
||||
|
||||
class PrinterData(BaseModel):
|
||||
"""
|
||||
Printer-specific data linked to Machine table.
|
||||
|
||||
Printers are stored in the machines table (machinetype.category = 'Printer').
|
||||
This table only holds printer-specific fields not in machines.
|
||||
|
||||
IP address is stored in the communications table.
|
||||
Zabbix data is queried in real-time via API (not cached here).
|
||||
"""
|
||||
__tablename__ = 'printerdata'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
# Link to machine
|
||||
machineid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('machines.machineid', ondelete='CASCADE'),
|
||||
unique=True,
|
||||
nullable=False,
|
||||
index=True
|
||||
)
|
||||
|
||||
# Windows/Network naming
|
||||
windowsname = db.Column(
|
||||
db.String(255),
|
||||
comment='Windows printer name (e.g., \\\\server\\printer)'
|
||||
)
|
||||
sharename = db.Column(
|
||||
db.String(100),
|
||||
comment='CSF/share name'
|
||||
)
|
||||
|
||||
# Installation
|
||||
iscsf = db.Column(db.Boolean, default=False, comment='Is CSF printer')
|
||||
installpath = db.Column(db.String(255), comment='Driver install path')
|
||||
|
||||
# Printer PIN (for secure print)
|
||||
pin = db.Column(db.String(20))
|
||||
|
||||
# Relationship
|
||||
machine = db.relationship(
|
||||
'Machine',
|
||||
backref=db.backref('printerdata', uselist=False, lazy='joined')
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
db.Index('idx_printer_windowsname', 'windowsname'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PrinterData machineid={self.machineid}>"
|
||||
174
plugins/printers/plugin.py
Normal file
174
plugins/printers/plugin.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""Printers plugin main class."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional, Type
|
||||
|
||||
from flask import Flask, Blueprint
|
||||
import click
|
||||
|
||||
from shopdb.plugins.base import BasePlugin, PluginMeta
|
||||
from shopdb.extensions import db
|
||||
from shopdb.core.models.machine import MachineType
|
||||
|
||||
from .models import PrinterData
|
||||
from .api import printers_bp
|
||||
from .services import ZabbixService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PrintersPlugin(BasePlugin):
|
||||
"""
|
||||
Printers plugin - extends machines with printer-specific functionality.
|
||||
|
||||
Printers use the unified Machine model with machinetype.category = 'Printer'.
|
||||
This plugin adds:
|
||||
- PrinterData table for printer-specific fields (windowsname, sharename, etc.)
|
||||
- Zabbix integration for real-time supply level lookups
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._manifest = self._load_manifest()
|
||||
self._zabbixservice = None
|
||||
|
||||
def _load_manifest(self) -> Dict:
|
||||
"""Load plugin manifest from JSON file."""
|
||||
manifestpath = Path(__file__).parent / 'manifest.json'
|
||||
if manifestpath.exists():
|
||||
with open(manifestpath, 'r') as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
@property
|
||||
def meta(self) -> PluginMeta:
|
||||
"""Return plugin metadata."""
|
||||
return PluginMeta(
|
||||
name=self._manifest.get('name', 'printers'),
|
||||
version=self._manifest.get('version', '1.0.0'),
|
||||
description=self._manifest.get(
|
||||
'description',
|
||||
'Printer management with Zabbix integration'
|
||||
),
|
||||
author=self._manifest.get('author', 'ShopDB Team'),
|
||||
dependencies=self._manifest.get('dependencies', []),
|
||||
core_version=self._manifest.get('core_version', '>=1.0.0'),
|
||||
api_prefix=self._manifest.get('api_prefix', '/api/printers'),
|
||||
)
|
||||
|
||||
def get_blueprint(self) -> Optional[Blueprint]:
|
||||
"""Return Flask Blueprint with API routes."""
|
||||
return printers_bp
|
||||
|
||||
def get_models(self) -> List[Type]:
|
||||
"""Return list of SQLAlchemy model classes."""
|
||||
return [PrinterData]
|
||||
|
||||
def get_services(self) -> Dict[str, Type]:
|
||||
"""Return plugin services."""
|
||||
return {
|
||||
'zabbix': ZabbixService,
|
||||
}
|
||||
|
||||
@property
|
||||
def zabbixservice(self) -> ZabbixService:
|
||||
"""Get Zabbix service instance."""
|
||||
if self._zabbixservice is None:
|
||||
self._zabbixservice = ZabbixService()
|
||||
return self._zabbixservice
|
||||
|
||||
def init_app(self, app: Flask, db_instance) -> None:
|
||||
"""Initialize plugin with Flask app."""
|
||||
app.config.setdefault('ZABBIX_URL', '')
|
||||
app.config.setdefault('ZABBIX_TOKEN', '')
|
||||
logger.info(f"Printers plugin initialized (v{self.meta.version})")
|
||||
|
||||
def on_install(self, app: Flask) -> None:
|
||||
"""Called when plugin is installed."""
|
||||
with app.app_context():
|
||||
self._ensureprintertypes()
|
||||
logger.info("Printers plugin installed")
|
||||
|
||||
def _ensureprintertypes(self) -> None:
|
||||
"""Ensure basic printer machine types exist."""
|
||||
printertypes = [
|
||||
('Laser Printer', 'Printer', 'Standard laser printer'),
|
||||
('Inkjet Printer', 'Printer', 'Inkjet printer'),
|
||||
('Label Printer', 'Printer', 'Label/barcode printer'),
|
||||
('Multifunction Printer', 'Printer', 'MFP with scan/copy/fax'),
|
||||
('Plotter', 'Printer', 'Large format plotter'),
|
||||
]
|
||||
|
||||
for name, category, description in printertypes:
|
||||
existing = MachineType.query.filter_by(machinetype=name).first()
|
||||
if not existing:
|
||||
mt = MachineType(
|
||||
machinetype=name,
|
||||
category=category,
|
||||
description=description,
|
||||
icon='printer'
|
||||
)
|
||||
db.session.add(mt)
|
||||
logger.debug(f"Created machine type: {name}")
|
||||
|
||||
db.session.commit()
|
||||
|
||||
def on_uninstall(self, app: Flask) -> None:
|
||||
"""Called when plugin is uninstalled."""
|
||||
logger.info("Printers plugin uninstalled")
|
||||
|
||||
def get_cli_commands(self) -> List:
|
||||
"""Return CLI commands for this plugin."""
|
||||
|
||||
@click.group('printers')
|
||||
def printerscli():
|
||||
"""Printers plugin commands."""
|
||||
pass
|
||||
|
||||
@printerscli.command('check-supplies')
|
||||
@click.argument('ip')
|
||||
def checksupplies(ip):
|
||||
"""Check supply levels for a printer by IP (via Zabbix)."""
|
||||
from flask import current_app
|
||||
|
||||
with current_app.app_context():
|
||||
service = ZabbixService()
|
||||
|
||||
if not service.isconfigured:
|
||||
click.echo('Error: Zabbix not configured. Set ZABBIX_URL and ZABBIX_TOKEN.')
|
||||
return
|
||||
|
||||
supplies = service.getsuppliesbyip(ip)
|
||||
if not supplies:
|
||||
click.echo(f'No supply data found for {ip}')
|
||||
return
|
||||
|
||||
click.echo(f'Supply levels for {ip}:')
|
||||
for supply in supplies:
|
||||
click.echo(f" {supply['name']}: {supply['level']}%")
|
||||
|
||||
return [printerscli]
|
||||
|
||||
def get_dashboard_widgets(self) -> List[Dict]:
|
||||
"""Return dashboard widget definitions."""
|
||||
return [
|
||||
{
|
||||
'name': 'Printer Status',
|
||||
'component': 'PrinterStatusWidget',
|
||||
'endpoint': '/api/printers/dashboard/summary',
|
||||
'size': 'medium',
|
||||
'position': 10,
|
||||
},
|
||||
]
|
||||
|
||||
def get_navigation_items(self) -> List[Dict]:
|
||||
"""Return navigation menu items."""
|
||||
return [
|
||||
{
|
||||
'name': 'Printers',
|
||||
'icon': 'printer',
|
||||
'route': '/printers',
|
||||
'position': 20,
|
||||
},
|
||||
]
|
||||
1
plugins/printers/schemas/__init__.py
Normal file
1
plugins/printers/schemas/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Printers plugin schemas (for future Marshmallow serialization)."""
|
||||
5
plugins/printers/services/__init__.py
Normal file
5
plugins/printers/services/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Printers plugin services."""
|
||||
|
||||
from .zabbix_service import ZabbixService
|
||||
|
||||
__all__ = ['ZabbixService']
|
||||
133
plugins/printers/services/zabbix_service.py
Normal file
133
plugins/printers/services/zabbix_service.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""Zabbix service for real-time printer supply lookups."""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import requests
|
||||
from flask import current_app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ZabbixService:
|
||||
"""
|
||||
Zabbix API service for real-time printer supply lookups.
|
||||
|
||||
Queries Zabbix by IP address to get current supply levels.
|
||||
No caching - always returns live data.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._url = None
|
||||
self._token = None
|
||||
|
||||
@property
|
||||
def isconfigured(self) -> bool:
|
||||
"""Check if Zabbix is configured."""
|
||||
self._url = current_app.config.get('ZABBIX_URL')
|
||||
self._token = current_app.config.get('ZABBIX_TOKEN')
|
||||
return bool(self._url and self._token)
|
||||
|
||||
def _apicall(self, method: str, params: Dict) -> Optional[Dict]:
|
||||
"""Make a Zabbix API call."""
|
||||
if not self.isconfigured:
|
||||
return None
|
||||
|
||||
payload = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': method,
|
||||
'params': params,
|
||||
'auth': self._token,
|
||||
'id': 1
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self._url}/api_jsonrpc.php",
|
||||
json=payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if 'error' in data:
|
||||
logger.error(f"Zabbix API error: {data['error']}")
|
||||
return None
|
||||
|
||||
return data.get('result')
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Zabbix API request failed: {e}")
|
||||
return None
|
||||
|
||||
def gethostbyip(self, ip: str) -> Optional[Dict]:
|
||||
"""Find a Zabbix host by IP address."""
|
||||
result = self._apicall('host.get', {
|
||||
'output': ['hostid', 'host', 'name'],
|
||||
'filter': {'ip': ip},
|
||||
'selectInterfaces': ['ip']
|
||||
})
|
||||
|
||||
if result:
|
||||
return result[0] if result else None
|
||||
return None
|
||||
|
||||
def getsuppliesbyip(self, ip: str) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Get printer supply levels by IP address.
|
||||
|
||||
Returns list of supplies with name and level percentage.
|
||||
"""
|
||||
# Find host by IP
|
||||
host = self.gethostbyip(ip)
|
||||
if not host:
|
||||
logger.debug(f"No Zabbix host found for IP {ip}")
|
||||
return None
|
||||
|
||||
hostid = host['hostid']
|
||||
|
||||
# Get supply-related items
|
||||
items = self._apicall('item.get', {
|
||||
'output': ['itemid', 'name', 'lastvalue', 'key_'],
|
||||
'hostids': hostid,
|
||||
'search': {
|
||||
'key_': 'supply' # Common key pattern for printer supplies
|
||||
},
|
||||
'searchWildcardsEnabled': True
|
||||
})
|
||||
|
||||
if not items:
|
||||
# Try alternate patterns
|
||||
items = self._apicall('item.get', {
|
||||
'output': ['itemid', 'name', 'lastvalue', 'key_'],
|
||||
'hostids': hostid,
|
||||
'search': {
|
||||
'name': 'toner'
|
||||
},
|
||||
'searchWildcardsEnabled': True
|
||||
})
|
||||
|
||||
if not items:
|
||||
return []
|
||||
|
||||
supplies = []
|
||||
for item in items:
|
||||
try:
|
||||
level = int(float(item.get('lastvalue', 0)))
|
||||
except (ValueError, TypeError):
|
||||
level = 0
|
||||
|
||||
supplies.append({
|
||||
'name': item.get('name', 'Unknown'),
|
||||
'level': level,
|
||||
'itemid': item.get('itemid'),
|
||||
'key': item.get('key_'),
|
||||
})
|
||||
|
||||
return supplies
|
||||
|
||||
def gethostid(self, ip: str) -> Optional[str]:
|
||||
"""Get Zabbix host ID for an IP address."""
|
||||
host = self.gethostbyip(ip)
|
||||
return host['hostid'] if host else None
|
||||
Reference in New Issue
Block a user