Files
shopdb-flask/plugins/slides/plugin.py
cproudlock 78a0ee8d83 Add custom fields + warranty plugin, rework settings into two-pane shell
Feature work from the 2026-07 session:

Settings IA
- Replace the flat 27-card settings hub with a persistent two-pane shell
  (SettingsLayout.vue): grouped, searchable left rail + content pane.
- Nest all settings/* routes under the shell via router post-processing;
  shared nav catalog in settingsNav.js. Group by asset class (PCs, Printers,
  Equipment, Network) so per-type settings stop scattering.

Custom fields (core)
- customfields + customfieldvalues tables (migration 7d14), CRUD API at
  /api/customfields, per-asset value get/save.
- Settings management page + reusable CustomFieldsSection (detail) and
  CustomFieldsInputs (form) wired into all four asset types.

Warranty (new plugin)
- plugins/warranty: warranties + warrantyassets (migration 7d15), derived
  coverage status, provider abstraction (manual now; Dell/Lenovo/HP stubs).
- API CRUD + per-asset panel + report buckets; WarrantyPanel on all four
  detail pages; Warranties management page; Warranty report + Reports card.
- Seed warranty.* permissions.

Printer drivers
- printerdrivers table (migration 7d13) linked to printer models; drivers now
  surface on the matching printer's detail page.

Other
- PCDetail rebalanced (Network + Status + Warranty + custom fields on the right).
- Rename PCs list "Features" column to "Remote Access"; fix badge hover underline.
- Drop equipment islocationonly field.
- Centralize asset-type label/route maps into utils/assetTypes.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:37:21 -04:00

75 lines
2.3 KiB
Python

"""Slides plugin main class.
Display/service plugin: contributes a blueprint only (no model, no AssetType,
no nav). Serves TV-dashboard slideshow images. Demonstrates the minimal plugin
shape - a feature that is purely an API surface.
"""
import json
import logging
from pathlib import Path
from typing import List, Dict, Optional, Type
from flask import Flask, Blueprint
from shopdb.plugins.base import BasePlugin, PluginMeta
from .api import slides_bp
from .models import TvSlide
logger = logging.getLogger(__name__)
class SlidesPlugin(BasePlugin):
"""Slides plugin - TV dashboard slideshow images."""
def __init__(self):
self._manifest = self._load_manifest()
def _load_manifest(self) -> Dict:
"""Load plugin manifest from JSON file."""
manifest_path = Path(__file__).parent / 'manifest.json'
if manifest_path.exists():
with open(manifest_path, 'r') as f:
return json.load(f)
return {}
@property
def meta(self) -> PluginMeta:
"""Return plugin metadata."""
return PluginMeta(
name=self._manifest.get('name', 'slides'),
version=self._manifest.get('version', '1.0.0'),
description=self._manifest.get(
'description',
'TV dashboard slideshow images'
),
author=self._manifest.get('author', 'ShopDB Team'),
dependencies=self._manifest.get('dependencies', []),
core_version=self._manifest.get('core_version', '>=0.1.0,<1.0.0'),
api_prefix=self._manifest.get('api_prefix', '/api/slides'),
)
def get_blueprint(self) -> Optional[Blueprint]:
"""Return Flask Blueprint with API routes."""
return slides_bp
def get_models(self) -> List[Type]:
"""Slide playlist metadata (image files live on disk)."""
return [TvSlide]
def get_navigation_items(self) -> List[Dict]:
"""Sidebar entry for the slide manager (admin)."""
return [
{
'name': 'Slides',
'icon': 'image',
'route': '/settings/slides',
'position': 7,
},
]
def init_app(self, app: Flask, db_instance) -> None:
"""Initialize plugin with Flask app."""
logger.info(f"Slides plugin initialized (v{self.meta.version})")