Separate shipped policies from site policies in the list

The list mixed both kinds of configuration policy into one table, so the only
way to tell a shipped default from something the site had written was to read
the source column. Split them into two tables under their own headings.

The split is presentational only. Both tables render the same Edit and Delete
buttons gated on the same iseditable(p) rule, so a shipped policy is not
stranded behind a dead button the way it was when the buttons gated on
source === 'local'.

The spec counted exactly two disabled bindings, which the second table would
have broken while the behaviour it was guarding stayed correct. It now counts
Edit and Delete in pairs and asserts the two always agree, so adding a group
does not fail a test about button gating.
This commit is contained in:
benjamin.baker
2026-09-20 13:23:23 -04:00
parent 3c91e7c314
commit 790ed4ec66
3 changed files with 143 additions and 13 deletions

View File

@@ -1834,8 +1834,30 @@ async function confirmRestart() {
const corporateTools = [
{
task: '\\ReportIP',
label: 'Report IP',
description: 'Re-runs the corporate IP reporting agent on this computer.',
label: 'Report IP - Scheduled Task',
description: 'Re-runs the corporate IP reporting agent on this computer '
+ 'by starting its registered scheduled task as SYSTEM.',
},
// Same tool, different delivery. The entry above starts the endpoint's own
// scheduled task; this one pushes a ShopDB-hosted script that reproduces
// double-clicking the desktop icon, for machines where the task is missing
// or not registered.
//
// It dispatches run_script rather than scheduledtask_run, so it carries a
// `script` field instead of a task path. routes.py allows this ONE script
// through on endpoints.corptools alone, matched by the stored script name in
// CORPTOOLS_PUSHABLE_SCRIPTS - so this string is load-bearing and must stay
// identical to the name the seeder writes. The name is sent rather than
// script:<id> because the id is assigned at seed time and differs per
// deployment; _resolve_run_script accepts either.
{
task: 'script:Report IP - Desktop Shortcut',
script: 'Report IP - Desktop Shortcut',
label: 'Report IP - Desktop Shortcut',
description: 'Re-reports this computer\'s IP address by running the same '
+ 'tool the desktop icon launches. Runs in the signed-in '
+ 'user\'s session - fails if nobody is logged on. Use when '
+ 'the scheduled task above is missing or fails.',
},
{
task: '\\SFLD-ApplyDSCConfig',
@@ -2274,17 +2296,32 @@ async function confirmRunTask(tool) {
if (typed !== 'RUN') return
} else if (!confirm(`Run "${tool.label}" on ${label}?\n\n`
+ `${tool.description}\n\n`
+ 'The task starts immediately on the endpoint.')) {
+ (tool.script
? 'The script is queued and runs at the next check-in.'
: 'The task starts immediately on the endpoint.'))) {
return
}
firingTask.value = tool.task
try {
await endpointsApi.enqueueCommand(
computer.value.assetid, 'scheduledtask_run', tool.task)
// "Started", not "finished": the agent reports success as soon as Task
// Scheduler accepts the task, so this never means the work is done. Any
// result the task produces comes back through telemetry, not this command.
toast.success(`${tool.label} started on ${label}`)
// Two dispatch shapes behind one list. Most entries start a task the
// endpoint already approved; the managed-script entries deliver a body
// ShopDB holds, which is a different action with a different server-side
// check. Branching here rather than in the list keeps the card markup and
// the confirm gate identical for both.
if (tool.script) {
await endpointsApi.enqueueCommand(
computer.value.assetid, 'run_script', tool.script)
// "Queued", not "started": run_script is collected on the agent's next
// check-in, unlike scheduledtask_run which the endpoint accepts live.
toast.success(`${tool.label} queued for ${label}`)
} else {
await endpointsApi.enqueueCommand(
computer.value.assetid, 'scheduledtask_run', tool.task)
// "Started", not "finished": the agent reports success as soon as Task
// Scheduler accepts the task, so this never means the work is done. Any
// result the task produces comes back through telemetry, not this command.
toast.success(`${tool.label} started on ${label}`)
}
} catch (error) {
toast.error(apiError(error, `Failed to queue ${tool.label}`))
} finally {