From 31dc9d49e1518c33da7b0399e2b08e48e88235e2 Mon Sep 17 00:00:00 2001 From: "benjamin.baker" Date: Mon, 7 Sep 2026 13:03:07 -0400 Subject: [PATCH] Add an All PCs policy scope that excludes servers The policy builder only offered "Matches everything" (the `true` scope node), which also targets servers. Authoring an estate-wide policy almost always means "every PC an operator sits at", and a reboot or script push against a server is a materially different event. Adds an `allpcs` scope node that matches every endpoint except those whose computer type is server-designated. Server designation reads the computer type's ICON slug, which is nominally a UI hint and is now load-bearing; it was chosen over matching the type NAME (a site can rename "Server") and over a new isserver column (needs a migration). It is isolated behind SERVER_ICON so promoting it to a real flag later is a one-place change. The `true` node is retained and still matches servers, so existing policies keep their exact meaning rather than silently re-scoping. It stays visible in the dropdown only when editing a policy that already uses it; new policies default to `allpcs`. Covered by tests/test_plugins/test_computers_policy_allpcs.py, verified against a planted canary so the server-exclusion assertions are known to bite. --- .../views/ConfigurationPoliciesList.vue | 4 +- .../frontend/views/ScopeNodeEditor.vue | 11 +-- plugins/computers/services/policyresolver.py | 10 ++- plugins/computers/services/policyscope.py | 29 ++++++- .../test_computers_policy_allpcs.py | 87 +++++++++++++++++++ 5 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 tests/test_plugins/test_computers_policy_allpcs.py diff --git a/plugins/computers/frontend/views/ConfigurationPoliciesList.vue b/plugins/computers/frontend/views/ConfigurationPoliciesList.vue index 2dfcd5b3..45eb98ae 100644 --- a/plugins/computers/frontend/views/ConfigurationPoliciesList.vue +++ b/plugins/computers/frontend/views/ConfigurationPoliciesList.vue @@ -140,7 +140,7 @@ const error = ref('') const form = ref(emptyForm()) function emptyForm() { - return { name: '', description: '', scopejson: { type: 'true' }, actions: [] } + return { name: '', description: '', scopejson: { type: 'allpcs' }, actions: [] } } onMounted(() => loadData()) @@ -221,7 +221,7 @@ function openModal(item = null) { form.value = { name: item.name || '', description: item.description || '', - scopejson: item.scopejson ? JSON.parse(JSON.stringify(item.scopejson)) : { type: 'true' }, + scopejson: item.scopejson ? JSON.parse(JSON.stringify(item.scopejson)) : { type: 'allpcs' }, actions: (item.actions || []).map(a => ({ action: a.action, mode: a.mode || 'gate', desiredstate: a.desiredstate, params: a.params, intervalminutes: a.intervalminutes diff --git a/plugins/computers/frontend/views/ScopeNodeEditor.vue b/plugins/computers/frontend/views/ScopeNodeEditor.vue index 439a43ed..f93d8e65 100644 --- a/plugins/computers/frontend/views/ScopeNodeEditor.vue +++ b/plugins/computers/frontend/views/ScopeNodeEditor.vue @@ -2,7 +2,8 @@