Consolidate settings into 8 hubs and fix security vulnerabilities

Settings Consolidation (37+ pages to 8 hubs):
- Asset Type Hub: Unified type management with plugin descriptors
- Model Catalog Hub: Vendors, types, models, operating systems
- Asset Configuration Hub: Statuses, relationships, fields, identifiers
- Security Hub: Authentication, tokens, users, audit
- Site Configuration Hub: Site settings, floor map, branding, imports
- Integrations Hub: ServiceNow, Zabbix, Dell warranty
- Locations Hub: Locations, business units, employees
- Network Infrastructure Hub: VLANs, subnets

Plugin Descriptor Pattern:
- Add get_asset_type_descriptor hook to BasePlugin
- Implement descriptors in 6 plugins (printers, computers, machines, network, measuringtools, locations)
- Add /api/pluginui/asset-type-descriptors and core-type-descriptors endpoints
- Generic SubtypesTab component renders CRUD UI from descriptor schema

Security Fixes:
- Fix stored XSS via javascript: URIs in Models and Support Teams
- Add URL scheme validation (allow only http/https) in create/update endpoints
- Fix plugin contract violations in endpoints plugin (use shopdb.api imports)
- Add missing requiresAdmin guard to printer model supplies route
- Add user-facing error messages to API token load failures

Integration Improvements:
- Add persistent tab navigation to ServiceNow, Zabbix, and Dell Warranty settings
- Standardize integration settings UI across all three pages
- Fix tab visibility when navigating between integrations

Tested with 94-agent orchestrated review for ADR compliance and security.
This commit is contained in:
Proudlock
2026-09-11 10:45:12 -04:00
parent 9f78d0bfc7
commit f9f1d39f08
34 changed files with 2485 additions and 166 deletions

View File

@@ -26,6 +26,12 @@ export default [
component: () => import('./views/ZabbixSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'printers' }
},
{
path: 'settings/integrations/zabbix',
name: 'integrations-zabbix',
component: () => import('./views/ZabbixSettings.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'printers' }
},
{
path: 'settings/printer-alerts',
name: 'printer-alerts',
@@ -61,7 +67,7 @@ export default [
path: 'settings/modelsupplies',
name: 'model-supplies',
component: () => import('./views/ModelSuppliesList.vue'),
meta: { requiresAuth: true, plugin: 'printers' }
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'printers' }
},
{
path: 'settings/printerdrivers',

View File

@@ -1,7 +1,32 @@
<template>
<div>
<div class="page-header">
<h2>Zabbix Supplies</h2>
<h2>Integrations</h2>
<p class="header-description">Configure external service connections and APIs</p>
</div>
<div class="integration-tabs">
<router-link
:to="{ name: 'servicenow-settings' }"
class="integration-tab"
:class="{ active: $route.name === 'servicenow-settings' }"
>
ServiceNow
</router-link>
<router-link
:to="{ name: 'zabbix-settings' }"
class="integration-tab"
:class="{ active: $route.name === 'zabbix-settings' }"
>
Zabbix Supplies
</router-link>
<router-link
:to="{ name: 'dell-warranty-settings' }"
class="integration-tab"
:class="{ active: $route.name === 'dell-warranty-settings' }"
>
Dell Warranty
</router-link>
</div>
<div class="section-card">
@@ -88,3 +113,39 @@ const zabbixMessage = computed(() => {
onMounted(loadSettings)
</script>
<style scoped>
.header-description {
margin: 0.5rem 0 0;
color: var(--text-light);
font-size: 0.95rem;
}
.integration-tabs {
display: flex;
gap: 0.25rem;
border-bottom: 1px solid var(--border-color);
background: var(--bg-secondary);
margin: 2rem 0;
}
.integration-tab {
padding: 0.75rem 1.5rem;
text-decoration: none;
color: var(--text-light);
border-bottom: 2px solid transparent;
transition: all 0.15s;
font-weight: 500;
font-size: 0.95rem;
}
.integration-tab:hover {
color: var(--text);
background: var(--hover-bg);
}
.integration-tab.active {
color: var(--primary);
border-bottom-color: var(--primary);
}
</style>

View File

@@ -678,3 +678,16 @@ class PrintersPlugin(BasePlugin):
return None
# _computer_by_hostname returns the (Computer, Asset) pair.
return row[1].assetid
def get_asset_type_descriptor(self):
"""Return descriptor for printer types management in Asset Type Hub."""
return {
'assettype': 'printer',
'subtype_endpoint': '/printers/types',
'subtype_id_field': 'printertypeid',
'subtype_label_field': 'printertype',
'subtype_description_field': 'description',
'subtype_color_field': 'color',
'optional_tabs': [],
'edit_permission': 'printers.manage',
}