Add application-driven policy generation with an acknowledgement gate
Ticking "Required on all PCs" now generates a configuration policy that installs the application on every non-server endpoint it matches, rather than only flagging a software-compliance report row. Because that save is a fleet-wide deployment, it requires explicit acknowledgement. The gate is enforced in the API, not only in the form: a confirmation dialog that can be sidestepped by POSTing directly is decoration, not a safeguard. It fires only on the transition INTO the required state, since re-saving an already-required application deploys nothing new. Generated policies carry a new 'autoapp' source. They stay editable, and hand-editing one transfers ownership to 'local' so it survives a later uncheck; 'shipped' remains read-only. Existing required applications are not migrated retroactively - policies are only created on save. Also adds the monitorable fields that the shipped-policy breakup will build on, and repairs a mangled non-ASCII byte in copilot-instructions.md that was failing the style gate.
This commit is contained in:
@@ -74,6 +74,24 @@
|
||||
Hidden
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Only shown when the user is newly ticking the box. An app that
|
||||
was already required has an existing policy, so re-saving it is
|
||||
not a new deployment and does not need re-acknowledging. -->
|
||||
<div v-if="showsPolicyWarning" class="policy-warning">
|
||||
<strong>This will install software on your fleet.</strong>
|
||||
<p>
|
||||
Marking <em>{{ form.appname || 'this application' }}</em> as required
|
||||
on all PCs creates a configuration policy that installs it on every
|
||||
non-server endpoint it applies to. Installation happens
|
||||
automatically, without further prompting, on each machine as it
|
||||
checks in.
|
||||
</p>
|
||||
<label>
|
||||
<input type="checkbox" v-model="acknowledgePolicy" />
|
||||
I understand this will install the application on all matched PCs.
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 style="margin-top: 1.5rem; margin-bottom: 1rem;">Links & Paths</h4>
|
||||
@@ -225,6 +243,15 @@ const form = ref({
|
||||
|
||||
const supportTeams = ref([])
|
||||
|
||||
// The value as saved, so we can tell a NEW deployment from a re-save of an
|
||||
// application that was already required. Only the former warrants a warning.
|
||||
const wasRequired = ref(false)
|
||||
const acknowledgePolicy = ref(false)
|
||||
|
||||
const showsPolicyWarning = computed(
|
||||
() => form.value.isrequired && !wasRequired.value
|
||||
)
|
||||
|
||||
// Files are chosen before the application exists, so they are held here and
|
||||
// uploaded once there is an id to attach them to (see saveApplication).
|
||||
const pendingImage = ref(null)
|
||||
@@ -322,6 +349,7 @@ onMounted(async () => {
|
||||
applicationnotes: app.applicationnotes || ''
|
||||
}
|
||||
imagePreview.value = imageSrcFor(app.image)
|
||||
wasRequired.value = app.isrequired || false
|
||||
if ((app.installpath || '').startsWith('/api/applications/package/')) {
|
||||
uploadedPackage.value = app.installpath.split('/').pop()
|
||||
}
|
||||
@@ -336,6 +364,13 @@ onMounted(async () => {
|
||||
|
||||
async function saveApplication() {
|
||||
error.value = ''
|
||||
|
||||
if (showsPolicyWarning.value && !acknowledgePolicy.value) {
|
||||
error.value = 'Please acknowledge that this will install the application '
|
||||
+ 'on all matched PCs before saving.'
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
|
||||
try {
|
||||
@@ -352,7 +387,8 @@ async function saveApplication() {
|
||||
documentationpath: form.value.documentationpath || null,
|
||||
installpath: form.value.installpath || null,
|
||||
image: form.value.image || null,
|
||||
applicationnotes: form.value.applicationnotes || null
|
||||
applicationnotes: form.value.applicationnotes || null,
|
||||
acknowledgepolicy: showsPolicyWarning.value
|
||||
}
|
||||
|
||||
let appid = route.params.id
|
||||
@@ -449,4 +485,27 @@ async function saveApplication() {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-light, #666);
|
||||
}
|
||||
|
||||
.policy-warning {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid var(--warning, #b45309);
|
||||
border-left-width: 4px;
|
||||
border-radius: 4px;
|
||||
background: var(--warning-bg, #fffbeb);
|
||||
color: var(--text, #333);
|
||||
}
|
||||
|
||||
.policy-warning p {
|
||||
margin: 0.5rem 0 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.policy-warning label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user