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>
This commit is contained in:
@@ -7,6 +7,8 @@ from shopdb.extensions import db, cache
|
||||
from shopdb.core.models import Setting, AuditLog
|
||||
from shopdb.utils.responses import success_response, error_response, ErrorCodes
|
||||
|
||||
from shopdb.utils.authz import require_permission, require_role
|
||||
|
||||
settings_bp = Blueprint('settings', __name__)
|
||||
|
||||
# Cache key for settings
|
||||
@@ -100,6 +102,7 @@ def get_setting(key: str):
|
||||
|
||||
@settings_bp.route('/<key>', methods=['PUT'])
|
||||
@jwt_required()
|
||||
@require_permission('settings.edit')
|
||||
def update_setting(key: str):
|
||||
"""Update a setting value."""
|
||||
data = request.get_json()
|
||||
@@ -145,6 +148,7 @@ def update_setting(key: str):
|
||||
|
||||
@settings_bp.route('', methods=['POST'])
|
||||
@jwt_required()
|
||||
@require_permission('settings.edit')
|
||||
def create_setting():
|
||||
"""Create a new setting (admin only)."""
|
||||
data = request.get_json()
|
||||
@@ -209,9 +213,72 @@ def build_default_settings():
|
||||
for key, label in SEARCH_DOMAINS.items()
|
||||
]
|
||||
|
||||
# Facility floor-map blueprint. Each site instance (ADR-004) points these
|
||||
# at its own floor-plan image and pixel dimensions; the map frontend reads
|
||||
# them instead of hardcoding one facility's plan. Defaults are the West
|
||||
# Jefferson sitemap so an un-reconfigured install still renders.
|
||||
mapdefaults = [
|
||||
{
|
||||
'key': 'map_blueprint_light',
|
||||
'value': '/static/images/sitemap2025-light.png',
|
||||
'valuetype': 'string',
|
||||
'category': 'map',
|
||||
'description': 'Floor-map blueprint image (light theme) for this facility'
|
||||
},
|
||||
{
|
||||
'key': 'map_blueprint_dark',
|
||||
'value': '/static/images/sitemap2025-dark.png',
|
||||
'valuetype': 'string',
|
||||
'category': 'map',
|
||||
'description': 'Floor-map blueprint image (dark theme) for this facility'
|
||||
},
|
||||
{
|
||||
'key': 'map_width',
|
||||
'value': '3300',
|
||||
'valuetype': 'integer',
|
||||
'category': 'map',
|
||||
'description': 'Floor-map blueprint width in pixels (native size of the image)'
|
||||
},
|
||||
{
|
||||
'key': 'map_height',
|
||||
'value': '2550',
|
||||
'valuetype': 'integer',
|
||||
'category': 'map',
|
||||
'description': 'Floor-map blueprint height in pixels (native size of the image)'
|
||||
},
|
||||
]
|
||||
|
||||
# Site identity. Each instance (ADR-004) sets its own public URL - used for
|
||||
# QR codes and any absolute link the app emits - and facility name shown on
|
||||
# the shopfloor dashboard. Blank site_base_url falls back to the browsing
|
||||
# origin so nothing breaks before a site configures it.
|
||||
sitedefaults = [
|
||||
{
|
||||
'key': 'site_base_url',
|
||||
'value': '',
|
||||
'valuetype': 'string',
|
||||
'category': 'site',
|
||||
'description': 'Public base URL of this site (scheme + host), e.g. https://shopdb.example.net. Used for QR codes and absolute links. Blank = use the browsing origin.'
|
||||
},
|
||||
{
|
||||
'key': 'facility_name',
|
||||
'value': 'West Jefferson',
|
||||
'valuetype': 'string',
|
||||
'category': 'site',
|
||||
'description': 'Facility name shown on the shopfloor dashboard header'
|
||||
},
|
||||
{
|
||||
'key': 'pc_access_domain',
|
||||
'value': 'device.geaerospace.net',
|
||||
'valuetype': 'string',
|
||||
'category': 'site',
|
||||
'description': 'Domain appended to a PC hostname to build remote-access links (host.device.geaerospace.net). Blank = use the hostname as-is.'
|
||||
},
|
||||
]
|
||||
|
||||
# Collector pc-type -> ComputerType mapping is computers-plugin domain;
|
||||
# the plugin seeds pctypemap_<pxetype> settings on install.
|
||||
defaults = identifierdefaults + searchdefaults + [
|
||||
defaults = sitedefaults + identifierdefaults + searchdefaults + mapdefaults + [
|
||||
# Zabbix integration
|
||||
{
|
||||
'key': 'zabbix_enabled',
|
||||
@@ -363,6 +430,7 @@ def build_default_settings():
|
||||
|
||||
@settings_bp.route('/seed', methods=['POST'])
|
||||
@jwt_required()
|
||||
@require_permission('settings.edit')
|
||||
def seed_default_settings():
|
||||
"""Seed default settings if they don't exist."""
|
||||
created = 0
|
||||
|
||||
Reference in New Issue
Block a user