The sidebar hides what the caller cannot reach - that is what makes it useful to a guest, and what stops a member with a sparse role being offered links that can only answer 403. It reads the permission off the nav item to decide. Only four items ever declared one. Machines, PCs, Printers, Network, USB, Knowledge Base, Notifications, Calendar, Dashboard, Map, Applications and Reports declared nothing, so the filter had nothing to go on: granting the guest role machines.view opened the API and left the sidebar empty, which reads as the grant not working. Every item now names its read permission - in the plugins, in the core navigation endpoint, and in the frontend's fallback list, which is what renders when the endpoint is unreachable and would otherwise disagree with it.
183 lines
7.0 KiB
Python
183 lines
7.0 KiB
Python
"""Measuring-tools plugin main class.
|
|
|
|
The first plugin built on the matured framework scaffold (ADR-005). It owns two
|
|
tables (measuringtooltypes, measuringtools), a blueprint under /api/measuringtools,
|
|
a sidebar entry, and a calibration report card. It seeds its own asset type and
|
|
a set of starter tool types on install.
|
|
"""
|
|
|
|
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 shopdb.api import db, AssetType
|
|
|
|
from .api import measuringtools_bp
|
|
from .models import MeasuringTool, MeasuringToolType
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Starter tool types seeded on install: (name, description, color).
|
|
# Colors are drawn from the shared frontend PALETTE (utils/colorStyle.js).
|
|
STARTER_TYPES = [
|
|
('Caliper', 'Vernier / digital caliper', '#14abef'),
|
|
('Micrometer', 'Outside / inside / depth micrometer', '#2dce89'),
|
|
('Thread Gage', 'Go / no-go thread gage', '#fb6340'),
|
|
('Bore Gage', 'Bore / hole diameter gage', '#7934f3'),
|
|
('Height Gage', 'Height / vertical measuring gage', '#ffc107'),
|
|
('Indicator', 'Dial / test indicator', '#11cdef'),
|
|
('Gage Block Set', 'Reference gage block set', '#e83e8c'),
|
|
('Other', 'Other measuring tool', '#6c757d'),
|
|
]
|
|
|
|
|
|
class MeasuringToolsPlugin(BasePlugin):
|
|
"""Metrology and inspection instruments (calibration lifecycle)."""
|
|
|
|
def __init__(self):
|
|
self._manifest = self._load_manifest()
|
|
|
|
def _load_manifest(self) -> Dict:
|
|
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 PluginMeta(
|
|
name=self._manifest.get('name', 'measuringtools'),
|
|
version=self._manifest.get('version', '1.0.0'),
|
|
description=self._manifest.get(
|
|
'description', 'Metrology and inspection instruments'),
|
|
author=self._manifest.get('author', 'ShopDB Team'),
|
|
dependencies=self._manifest.get('dependencies', []),
|
|
core_version=self._manifest.get('core_version', '>=0.6.0,<1.0.0'),
|
|
api_prefix=self._manifest.get('api_prefix', '/api/measuringtools'),
|
|
)
|
|
|
|
def get_blueprint(self) -> Optional[Blueprint]:
|
|
return measuringtools_bp
|
|
|
|
def get_models(self) -> List[Type]:
|
|
return [MeasuringTool, MeasuringToolType]
|
|
|
|
def get_navigation_items(self) -> List[Dict]:
|
|
return [
|
|
{
|
|
'name': 'Measuring Tools',
|
|
'icon': 'ruler',
|
|
'route': '/measuringtools',
|
|
'permission': 'measuringtools.view',
|
|
'position': 22,
|
|
'section': 'assets',
|
|
},
|
|
]
|
|
|
|
def get_reports(self) -> List[Dict]:
|
|
return [
|
|
{
|
|
'id': 'calibration',
|
|
'name': 'Calibration Due',
|
|
'description': 'Measuring tools bucketed by calibration status',
|
|
'category': 'compliance',
|
|
'route': '/reports/calibration',
|
|
'permission': 'measuringtools.view',
|
|
},
|
|
]
|
|
|
|
def get_config_schema(self) -> List[Dict]:
|
|
# No external credentials or endpoints: calibration is tracked by hand,
|
|
# so the setup wizard shows nothing to configure. Documented in the
|
|
# plugin guide as the intentional empty-schema case.
|
|
return []
|
|
|
|
def get_settings_cards(self) -> List[Dict]:
|
|
# ADR-010 pilot. Contributes the Measuring Tools settings card that used
|
|
# to be hardcoded in the core settingsNav.js catalog.
|
|
return [
|
|
{
|
|
'group': 'Measuring Tools',
|
|
'to': '/settings/measuringtooltypes',
|
|
'icon': 'ruler',
|
|
'title': 'Measuring Tool Types',
|
|
'description': 'Manage measuring-tool subtypes (caliper, '
|
|
'micrometer, thread gage...) + map colors',
|
|
'position': 22,
|
|
},
|
|
]
|
|
|
|
def get_asset_presentation(self) -> List[Dict]:
|
|
# ADR-010 pilot. Tells core how to render + link the measuring_tool
|
|
# asset type in global-search rows and cross-links.
|
|
# The consumer only substitutes {assetid} (search/cross-link rows carry
|
|
# the core asset id, not the extension id), so link through the by-asset
|
|
# resolver route rather than the id-keyed detail route.
|
|
return [
|
|
{
|
|
'assettype': 'measuring_tool',
|
|
'icon': 'ruler',
|
|
'label': 'Measuring Tool',
|
|
'route': '/measuringtools/by-asset/{assetid}',
|
|
},
|
|
]
|
|
|
|
def get_map_overlays(self) -> List[Dict]:
|
|
# ADR-010 pilot. Declares a calibration-due badge overlay for the
|
|
# shop-floor map; the map fetches the endpoint to decorate markers.
|
|
return [
|
|
{
|
|
'id': 'calibration-due',
|
|
'label': 'Calibration due',
|
|
'endpoint': '/api/measuringtools/map-overlay',
|
|
'style': 'badge',
|
|
'legend': True,
|
|
},
|
|
]
|
|
|
|
def init_app(self, app: Flask, db_instance) -> None:
|
|
logger.info(f"Measuring-tools plugin initialized (v{self.meta.version})")
|
|
|
|
def on_install(self, app: Flask) -> None:
|
|
with app.app_context():
|
|
self._ensure_asset_type()
|
|
self._ensure_starter_types()
|
|
db.session.commit()
|
|
logger.info("Measuring-tools plugin installed")
|
|
|
|
def _ensure_asset_type(self) -> None:
|
|
existing = AssetType.query.filter_by(assettype='measuring_tool').first()
|
|
if not existing:
|
|
db.session.add(AssetType(
|
|
assettype='measuring_tool',
|
|
pluginname='measuringtools',
|
|
tablename='measuringtools',
|
|
description='Metrology and inspection instruments (gauges, '
|
|
'calipers, thread gages, bore gages, ...)',
|
|
icon='ruler',
|
|
))
|
|
logger.debug("Created asset type: measuring_tool")
|
|
db.session.commit()
|
|
|
|
def _ensure_starter_types(self) -> None:
|
|
for name, description, color in STARTER_TYPES:
|
|
if not MeasuringToolType.query.filter_by(name=name).first():
|
|
db.session.add(MeasuringToolType(
|
|
name=name, description=description, color=color))
|
|
logger.debug(f"Created measuring-tool type: {name}")
|
|
db.session.commit()
|
|
|
|
def get_permissions(self) -> List:
|
|
"""Return the RBAC permissions this plugin owns."""
|
|
return [
|
|
('measuringtools.view', 'View measuring tools', 'measuringtools'),
|
|
('measuringtools.create', 'Create measuring tools', 'measuringtools'),
|
|
('measuringtools.edit', 'Edit measuring tools', 'measuringtools'),
|
|
('measuringtools.delete', 'Delete measuring tools', 'measuringtools'),
|
|
]
|