harden CSV import reconciliation and preserve saved model selections

Default fuzzy reconcile threshold to 0 and refuse merges when digit runs differ so numbered labels like Bay 311 never bind to Bay 310. Keep edit forms from clearing saved model fields when category filtering hides untyped rows. Includes in-progress computers policy and endpoints command feature work.
This commit is contained in:
benjamin.baker
2026-08-28 09:29:09 -04:00
parent d68c6db9ab
commit 01734e5449
46 changed files with 4241 additions and 52 deletions

View File

@@ -0,0 +1,106 @@
<template>
<div class="scope-node">
<div class="scope-node-row">
<select v-model="node.type" class="form-control scope-type" @change="onTypeChange">
<option value="true">Matches everything</option>
<option value="pctype">PC type is</option>
<option value="app">App is installed</option>
<option value="and">ALL of (AND)</option>
<option value="or">ANY of (OR)</option>
<option value="not">NOT</option>
</select>
<select v-if="node.type === 'pctype'" v-model.number="node.id" class="form-control">
<option :value="undefined" disabled>Select PC type...</option>
<option v-for="pt in pctypes" :key="pt.computertypeid" :value="pt.computertypeid">
{{ pt.computertype }}
</option>
</select>
<select v-if="node.type === 'app'" v-model.number="node.id" class="form-control">
<option :value="undefined" disabled>Select app...</option>
<option v-for="app in apps" :key="app.appid" :value="app.appid">
{{ app.appname }}
</option>
</select>
<button v-if="removable" type="button" class="btn btn-danger btn-sm" @click="$emit('remove')">
Remove
</button>
</div>
<div v-if="node.type === 'and' || node.type === 'or'" class="scope-children">
<ScopeNodeEditor
v-for="(child, index) in node.nodes"
:key="index"
:node="child"
:pctypes="pctypes"
:apps="apps"
:removable="true"
@remove="removeChild(index)"
/>
<button type="button" class="btn btn-secondary btn-sm" @click="addChild">+ Add condition</button>
</div>
<div v-if="node.type === 'not'" class="scope-children">
<ScopeNodeEditor
:node="node.node"
:pctypes="pctypes"
:apps="apps"
:removable="false"
/>
</div>
</div>
</template>
<script setup>
const props = defineProps({
node: { type: Object, required: true },
pctypes: { type: Array, default: () => [] },
apps: { type: Array, default: () => [] },
removable: { type: Boolean, default: false }
})
defineEmits(['remove'])
function onTypeChange() {
const node = props.node
// Reset payload keys so a switched node never carries stale fields from its
// previous type. Keep only what the new type needs.
delete node.id
delete node.nodes
delete node.node
if (node.type === 'and' || node.type === 'or') {
node.nodes = [{ type: 'true' }]
} else if (node.type === 'not') {
node.node = { type: 'true' }
}
}
function addChild() {
props.node.nodes.push({ type: 'true' })
}
function removeChild(index) {
props.node.nodes.splice(index, 1)
if (props.node.nodes.length === 0) {
props.node.nodes.push({ type: 'true' })
}
}
</script>
<script>
export default { name: 'ScopeNodeEditor' }
</script>
<style scoped>
.scope-node { margin: 4px 0; }
.scope-node-row { display: flex; gap: 8px; align-items: center; }
.scope-type { max-width: 220px; }
.scope-children {
margin-left: 24px;
padding-left: 12px;
border-left: 2px solid var(--border-color, #444);
margin-top: 6px;
}
</style>