Add per-domain global-search toggles
Lets an admin choose which content types appear in global search results, independent of whether the owning plugin is enabled (the existing _require_enabled gating was all-or-nothing per plugin). - settings.py: SEARCH_DOMAINS const (9 result types: application, knowledgebase, employee, equipment, computer, printer, network_device, notification, subnet) + seed keys search_<type>_enabled (boolean, default true) in build_default_settings (covers API seed + CLI). - search.py: global_search loads disabled types in one query (category 'search') and filters the deduped results by type before counts/truncation. Missing key = enabled. - SystemSettings.vue: "Global Search" section, one toggle per domain (mirrors the identifier pattern; create-on-404 fallback so an un-reseeded deploy still works). - Tests: domain included by default, disabled domain excluded, seed creates the 9 keys. 166 tests pass, naming green, build green. Verified live: toggling search_knowledgebase_enabled off drops knowledgebase from search counts and back on restores it. Dev DB seeded (9 keys). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -414,6 +414,32 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global Search Section -->
|
||||
<div class="section-card">
|
||||
<h2 class="section-title">Global Search</h2>
|
||||
|
||||
<div class="setting-group">
|
||||
<p class="setting-description">
|
||||
Choose which content types appear in global search results. Disabling a
|
||||
type here hides it from search without disabling the plugin elsewhere.
|
||||
</p>
|
||||
|
||||
<div class="setting-row" v-for="domain in searchDomains" :key="domain.key">
|
||||
<label class="toggle-label">
|
||||
<span>{{ domain.label }}</span>
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ active: searchValue(domain.key) }"
|
||||
@click="toggleSearchDomain(domain.key)"
|
||||
:disabled="saving"
|
||||
>
|
||||
<span class="toggle-slider"></span>
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-message">{{ error }}</div>
|
||||
@@ -476,6 +502,26 @@ function matrixValue(name, assettype) {
|
||||
return key in identifierMatrix ? identifierMatrix[key] : true
|
||||
}
|
||||
|
||||
// Global-search domain toggles: keys follow search_<type>_enabled.
|
||||
// Missing = enabled (default on).
|
||||
const searchDomains = [
|
||||
{ key: 'application', label: 'Applications' },
|
||||
{ key: 'knowledgebase', label: 'Knowledge Base' },
|
||||
{ key: 'employee', label: 'Employees' },
|
||||
{ key: 'equipment', label: 'Equipment' },
|
||||
{ key: 'computer', label: 'PCs' },
|
||||
{ key: 'printer', label: 'Printers' },
|
||||
{ key: 'network_device', label: 'Network Devices' },
|
||||
{ key: 'notification', label: 'Notifications' },
|
||||
{ key: 'subnet', label: 'Subnets' }
|
||||
]
|
||||
const searchMatrix = reactive({})
|
||||
|
||||
function searchValue(domainKey) {
|
||||
const key = `search_${domainKey}_enabled`
|
||||
return key in searchMatrix ? searchMatrix[key] : true
|
||||
}
|
||||
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const testingEmail = ref(false)
|
||||
@@ -538,6 +584,8 @@ async function loadSettings() {
|
||||
settings[setting.key] = setting.value
|
||||
} else if (/^identifier_.+_(equipment|computer|printer|network_device)_enabled$/.test(setting.key)) {
|
||||
identifierMatrix[setting.key] = setting.value !== false
|
||||
} else if (/^search_.+_enabled$/.test(setting.key)) {
|
||||
searchMatrix[setting.key] = setting.value !== false
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -591,6 +639,40 @@ async function toggleIdentifier(name, assettype) {
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleSearchDomain(domainKey) {
|
||||
const key = `search_${domainKey}_enabled`
|
||||
const newValue = !searchValue(domainKey)
|
||||
const label = searchDomains.find(d => d.key === domainKey)?.label || domainKey
|
||||
try {
|
||||
saving.value = true
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
try {
|
||||
await settingsApi.update(key, newValue)
|
||||
} catch (e) {
|
||||
if (e.response?.status === 404) {
|
||||
await settingsApi.create({
|
||||
key,
|
||||
value: newValue,
|
||||
valuetype: 'boolean',
|
||||
category: 'search',
|
||||
description: `Include ${label} in global search results`
|
||||
})
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
searchMatrix[key] = newValue
|
||||
success.value = 'Setting saved'
|
||||
setTimeout(() => { success.value = '' }, 2000)
|
||||
} catch (e) {
|
||||
error.value = e.response?.data?.message || 'Failed to save setting'
|
||||
console.error(e)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSetting(key, value) {
|
||||
try {
|
||||
saving.value = true
|
||||
|
||||
Reference in New Issue
Block a user