Let the policy list edit the rows the API accepts
The shipped Configuration Policies rendered with working-looking Edit and Delete buttons that did nothing. The API had already been widened to accept shipped rows, but the buttons still carried :disabled on source !== local, so the write was never sent and the change looked like it had never rolled. Gate both buttons on the set the API actually accepts. The shipped badge stays: it is provenance, not a lock, and it has to survive an edit so a site can still tell a product-supplied row from one of its own. Business Policies keep the local-only rule. Their API guard is unchanged and no shipped Site Policy exists now, so the UI already matches. Cover it with a spec, because the failure mode is a disabled attribute silently disagreeing with a server guard - nothing errors, the button just does nothing.
This commit is contained in:
@@ -42,8 +42,8 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="actions">
|
||||
<button class="btn btn-secondary btn-sm" :disabled="p.source !== 'local'" @click="openModal(p)">Edit</button>
|
||||
<button class="btn btn-danger btn-sm" :disabled="p.source !== 'local'" @click="remove(p)">Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" :disabled="!iseditable(p)" @click="openModal(p)">Edit</button>
|
||||
<button class="btn btn-danger btn-sm" :disabled="!iseditable(p)" @click="remove(p)">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="policies.length === 0">
|
||||
@@ -287,6 +287,18 @@ async function save() {
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors the API guard in plugins/computers/api/policy_routes.py: local,
|
||||
// autoapp and shipped rows are all editable. A shipped row is a starting point
|
||||
// the product supplies, not a fixed opinion, and it keeps source='shipped' so
|
||||
// provenance survives the edit. Only a genuinely external managed owner is
|
||||
// read-only. Disabling on source==='local' alone stranded shipped rows behind
|
||||
// dead buttons while the API was accepting the write.
|
||||
const EDITABLE_SOURCES = ['local', 'autoapp', 'shipped']
|
||||
|
||||
function iseditable(p) {
|
||||
return EDITABLE_SOURCES.includes(p.source)
|
||||
}
|
||||
|
||||
async function remove(p) {
|
||||
if (!confirm(`Delete configuration policy "${p.name}"?`)) return
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { readFileSync } from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname, join } from 'path'
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url))
|
||||
const view = readFileSync(join(here, 'ConfigurationPoliciesList.vue'), 'utf8')
|
||||
|
||||
// The API decides what may be edited; the buttons only reflect that decision.
|
||||
// These drifted once: policy_routes.py was widened to accept shipped rows while
|
||||
// the buttons still disabled on source !== 'local', so every shipped policy sat
|
||||
// behind a dead Edit button and the change looked like it had never rolled.
|
||||
// If the API guard changes, EDITABLE_SOURCES here changes with it.
|
||||
describe('the configuration policy buttons follow the API guard', () => {
|
||||
it('treats shipped as editable, not read-only', () => {
|
||||
expect(view).toContain("EDITABLE_SOURCES = ['local', 'autoapp', 'shipped']")
|
||||
})
|
||||
|
||||
it('does not gate the buttons on local alone', () => {
|
||||
expect(view).not.toContain(":disabled=\"p.source !== 'local'\"")
|
||||
})
|
||||
|
||||
it('gates edit and delete on the same rule', () => {
|
||||
const disabled = view.match(/:disabled="!iseditable\(p\)"/g) || []
|
||||
expect(disabled).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user