Merge the wilm delivery work into main
Ben Baker's installed-software collection, software delivery (run_script and
update_app), agentless adoption tool and command-queue hardening, with his
authorship preserved.
Reviewed before promotion. Fixed: the endpoints plugin tests were never collected
by pytest, the SBOM snapshot table's key would exhaust on MySQL, ingest did not
truncate two columns strict MySQL would reject, and update_app's version branch
pinned a digest for a file that route never serves. Agent packaging moved off
require_role('admin') onto a new endpoints.agentpackage permission.
Contract 0.27.0. Full suite 2231, frontend 314.
This commit is contained in:
@@ -687,6 +687,43 @@ export const applicationsApi = {
|
||||
}
|
||||
}
|
||||
|
||||
// ShopDB-hosted script library. Backs run_script delivery: the agent fetches a
|
||||
// script body by digest with its collector credential and runs it in the
|
||||
// script's configured run context (system or user).
|
||||
export const scriptsApi = {
|
||||
list(params = {}) {
|
||||
return api.get('/scripts', { params })
|
||||
},
|
||||
listAll(params = {}) {
|
||||
return fetchAllPages('/scripts', params)
|
||||
},
|
||||
get(id) {
|
||||
return api.get(`/scripts/${id}`)
|
||||
},
|
||||
create(data) {
|
||||
return api.post('/scripts', data)
|
||||
},
|
||||
update(id, data) {
|
||||
return api.put(`/scripts/${id}`, data)
|
||||
},
|
||||
delete(id) {
|
||||
return api.delete(`/scripts/${id}`)
|
||||
},
|
||||
// script-body upload; backend sets filename/sha256 to the stored payload.
|
||||
uploadBody(id, file, onProgress) {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
return api.post(`/scripts/${id}/body`, form, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
onUploadProgress: event => {
|
||||
if (onProgress && event.total) {
|
||||
onProgress(Math.round((event.loaded * 100) / event.total))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Support Teams API (teams + nested contacts)
|
||||
export const supportteamsApi = {
|
||||
list(params = {}) {
|
||||
|
||||
@@ -69,14 +69,24 @@
|
||||
<span class="assets-v">{{ stats.totalassets || 0 }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Status, on one line, and explicitly labelled as status. In
|
||||
repair is muted at zero rather than given a card of its own to
|
||||
announce that nothing is wrong. -->
|
||||
<p class="assets-status">
|
||||
<span class="assets-inuse">{{ stats.activeassets || 0 }}</span> in use
|
||||
<span aria-hidden="true"> / </span>
|
||||
<span :class="{ 'assets-zero': !stats.inrepair }">
|
||||
{{ stats.inrepair || 0 }} in repair
|
||||
<!-- Every status the total contains, not just In Use. These were
|
||||
counted server-side for the rollup and then thrown away, so a
|
||||
reader could see one number and had no way to learn where the
|
||||
rest of the total went. Retired assets in particular ARE in
|
||||
the total, which is not guessable. -->
|
||||
<div v-for="row in statusrows" :key="row.name" class="assets-row assets-sub">
|
||||
<span class="assets-k assets-subk">{{ row.name }}</span>
|
||||
<span class="assets-v assets-subv">{{ row.count }}</span>
|
||||
</div>
|
||||
|
||||
<!-- What the total covers. Asked twice what the number meant, which
|
||||
is the signal a label is underspecified. -->
|
||||
<p class="assets-caption">
|
||||
Active records only<span v-if="stats.dualpathcollapsed">, dual-bay
|
||||
pairs counted once</span>.
|
||||
<span v-if="stats.inactiveexcluded">
|
||||
{{ stats.inactiveexcluded }} deactivated record{{ stats.inactiveexcluded === 1 ? '' : 's' }}
|
||||
excluded.
|
||||
</span>
|
||||
</p>
|
||||
</section>
|
||||
@@ -133,6 +143,17 @@ const summaryloaded = ref(false)
|
||||
// hold - and to what the guest role reaches, when nobody is signed in.
|
||||
const navitems = ref([])
|
||||
|
||||
// Every status in the total, biggest first. Sorted rather than fixed-order
|
||||
// because the statuses are seed data a site can change - hardcoding "In Use,
|
||||
// In Repair, Retired" would silently omit a status somebody added.
|
||||
const statusrows = computed(() => {
|
||||
const statuses = stats.value.statuses || {}
|
||||
return Object.keys(statuses)
|
||||
.map((name) => ({ name, count: statuses[name] }))
|
||||
.filter((row) => row.count > 0)
|
||||
.sort((a, b) => b.count - a.count)
|
||||
})
|
||||
|
||||
const openable = computed(
|
||||
// The dashboard itself is not somewhere to go from the dashboard.
|
||||
() => navitems.value.filter((item) => item.route && item.route !== '/'))
|
||||
@@ -224,11 +245,18 @@ span.assets-k { color: var(--text); font-weight: 600; }
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.assets-status {
|
||||
margin: 0.5rem 0 0;
|
||||
font-size: 0.85rem;
|
||||
/* Status rows sit UNDER the total, indented and quieter, because they break
|
||||
the total down rather than adding to it. Same weight as the type rows would
|
||||
read as more things to add up. */
|
||||
.assets-sub { border-bottom: none; padding: 0.15rem 0 0.15rem 0.75rem; }
|
||||
.assets-subk { color: var(--text-light); font-size: 0.85rem; }
|
||||
a.assets-subk:hover { text-decoration: none; }
|
||||
.assets-subv { font-weight: 500; font-size: 0.85rem; color: var(--text-light); }
|
||||
|
||||
.assets-caption {
|
||||
margin: 0.6rem 0 0;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.45;
|
||||
color: var(--text-light);
|
||||
}
|
||||
.assets-inuse { color: var(--success); font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.assets-zero { color: var(--text-light); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user