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.
167 lines
5.1 KiB
Vue
167 lines
5.1 KiB
Vue
<template>
|
|
<div>
|
|
<div class="page-header">
|
|
<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">
|
|
<div class="setting-group">
|
|
<p class="setting-description">
|
|
Wire global search and ticket links to your ServiceNow instance. When
|
|
enabled, matching ticket numbers become clickable links and can trigger
|
|
a smart-redirect from global search. Defaults ship for GE; change them
|
|
for your site.
|
|
</p>
|
|
|
|
<div class="setting-row">
|
|
<label class="toggle-label">
|
|
<span>Enable ServiceNow</span>
|
|
<button
|
|
class="toggle-btn"
|
|
:class="{ active: settings.servicenow_enabled }"
|
|
@click="toggleSetting('servicenow_enabled')"
|
|
:disabled="saving"
|
|
>
|
|
<span class="toggle-slider"></span>
|
|
</button>
|
|
</label>
|
|
</div>
|
|
|
|
<template v-if="settings.servicenow_enabled">
|
|
<div class="setting-row">
|
|
<label>
|
|
<span>Search URL</span>
|
|
<input
|
|
type="url"
|
|
v-model="settings.servicenow_search_url"
|
|
placeholder="https://yourtenant.service-now.com/now/nav/ui/search/.../{ticket}/..."
|
|
@blur="saveSetting('servicenow_search_url', settings.servicenow_search_url)"
|
|
:disabled="saving"
|
|
>
|
|
<small class="input-hint">Global-search redirect target. Use {ticket} where the ticket number goes.</small>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="setting-row">
|
|
<label>
|
|
<span>Ticket Prefixes</span>
|
|
<input
|
|
type="text"
|
|
v-model="settings.servicenow_ticket_prefixes"
|
|
placeholder="GEINC,GECHG,GERIT,GESCT"
|
|
@blur="saveSetting('servicenow_ticket_prefixes', settings.servicenow_ticket_prefixes)"
|
|
:disabled="saving"
|
|
>
|
|
<small class="input-hint">Comma-separated ticket-number prefixes that this site recognizes.</small>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="setting-row">
|
|
<label>
|
|
<span>Incident URL</span>
|
|
<input
|
|
type="url"
|
|
v-model="settings.servicenow_incident_url"
|
|
placeholder="(blank = plain text; use {ticket} in a URL template)"
|
|
@blur="saveSetting('servicenow_incident_url', settings.servicenow_incident_url)"
|
|
:disabled="saving"
|
|
>
|
|
<small class="input-hint">Link template for incident tickets. Use {ticket} where the ticket number goes.</small>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="setting-row">
|
|
<label>
|
|
<span>Change URL</span>
|
|
<input
|
|
type="url"
|
|
v-model="settings.servicenow_change_url"
|
|
placeholder="(blank = plain text; use {ticket} in a URL template)"
|
|
@blur="saveSetting('servicenow_change_url', settings.servicenow_change_url)"
|
|
:disabled="saving"
|
|
>
|
|
<small class="input-hint">Link template for change tickets. Use {ticket} where the ticket number goes.</small>
|
|
</label>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="error" class="error-message">{{ error }}</div>
|
|
<div v-if="success" class="settings-success">{{ success }}</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted } from 'vue'
|
|
import { useSystemSettings } from '../../composables/systemSettings'
|
|
|
|
const {
|
|
settings, saving, error, success,
|
|
loadSettings, saveSetting, toggleSetting,
|
|
} = useSystemSettings()
|
|
|
|
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>
|