Give the backups plugin a settings page, and show where a snapshot is
This commit is contained in:
157
plugins/backups/frontend/views/BackupsSettings.vue
Normal file
157
plugins/backups/frontend/views/BackupsSettings.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<h2>Backups</h2>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="setting-group">
|
||||
<h3>Collection</h3>
|
||||
<p class="setting-description">
|
||||
How often a PC attempts to capture the config it backs up. GE-Enforce runs every
|
||||
cycle, so this is what stops a bay collecting several times a day. A capture that
|
||||
finds nothing changed writes no revision - it only records that the check happened.
|
||||
</p>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>Interval (hours)</span>
|
||||
<input type="number" min="1" v-model="values.backups_intervalhours"
|
||||
@blur="save('backups_intervalhours')" :disabled="saving">
|
||||
<small class="input-hint">Default 24. Read by the fleet from /api/settings/public.</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>Share root (UNC)</span>
|
||||
<input type="text" v-model="values.backups_shareroot"
|
||||
placeholder="\\fileserver\shopfloor\backup"
|
||||
@blur="save('backups_shareroot')" :disabled="saving">
|
||||
<small class="input-hint">
|
||||
Where opaque backups (part-marker snapshots) are written by the collecting PC.
|
||||
Blank until the site sets it - no default is right at more than one site.
|
||||
Advisory: each revision records the path it was actually written to.
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="setting-group">
|
||||
<h3>Health</h3>
|
||||
<p class="setting-description">
|
||||
A backup is judged on when it was last CONFIRMED, not when it last changed. An
|
||||
unchanged config writes no revision, so a machine stable for months is healthy -
|
||||
the one to worry about is the machine whose backup stopped running.
|
||||
</p>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>Stale after (days)</span>
|
||||
<input type="number" min="0" v-model="values.backups_staledays"
|
||||
@blur="save('backups_staledays')" :disabled="saving">
|
||||
<small class="input-hint">
|
||||
Default 3. Drives the fleet Backup badge and the dashboard card. 0 disables both.
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="setting-group">
|
||||
<h3>Retention</h3>
|
||||
<p class="setting-description">
|
||||
Both off by default, and dedup already keeps growth low - a revision appears only
|
||||
when a config really changed. Retention exists for a value that flaps. The newest
|
||||
and oldest revision of a chain are never pruned.
|
||||
</p>
|
||||
<p class="setting-description">
|
||||
<strong>Note:</strong> pruning removes database revisions only. Files written to the
|
||||
share are not deleted, so enabling this on a share-backed kind leaves snapshot
|
||||
directories with no revision pointing at them.
|
||||
</p>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>Keep at most (revisions per chain)</span>
|
||||
<input type="number" min="0" v-model="values.backups_retentioncount"
|
||||
@blur="save('backups_retentioncount')" :disabled="saving">
|
||||
<small class="input-hint">0 = keep everything.</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>Also drop older than (days)</span>
|
||||
<input type="number" min="0" v-model="values.backups_retentiondays"
|
||||
@blur="save('backups_retentiondays')" :disabled="saving">
|
||||
<small class="input-hint">0 = no age limit.</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-message">{{ error }}</div>
|
||||
<div v-if="success" class="settings-success">{{ success }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// The backups plugin ships five settings and, until now, no page for any of
|
||||
// them - they were reachable only through the API or a flask shell on the
|
||||
// server. The core settings catalog is hardcoded and cannot be extended by a
|
||||
// plugin, so this page is contributed the ADR-010 way: a settings card from
|
||||
// get_settings_cards plus a route here, both of which disappear when the
|
||||
// plugin is disabled.
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { settingsApi } from '@/api'
|
||||
import { apiError } from '@/utils/apiError'
|
||||
|
||||
const KEYS = [
|
||||
'backups_intervalhours',
|
||||
'backups_shareroot',
|
||||
'backups_staledays',
|
||||
'backups_retentioncount',
|
||||
'backups_retentiondays',
|
||||
]
|
||||
|
||||
const values = reactive({})
|
||||
const saving = ref(false)
|
||||
const error = ref('')
|
||||
const success = ref('')
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const response = await settingsApi.list()
|
||||
const rows = response.data?.data || response.data || []
|
||||
// The list endpoint returns every setting; pick out ours rather than
|
||||
// fetching five times.
|
||||
for (const key of KEYS) {
|
||||
const row = rows.find(item => item.key === key)
|
||||
values[key] = row ? row.value : ''
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = apiError(err, 'Could not load backup settings')
|
||||
}
|
||||
}
|
||||
|
||||
async function save(key) {
|
||||
saving.value = true
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
try {
|
||||
await settingsApi.update(key, values[key])
|
||||
success.value = 'Saved'
|
||||
setTimeout(() => { success.value = '' }, 2000)
|
||||
} catch (err) {
|
||||
error.value = apiError(err, `Could not save ${key}`)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
Reference in New Issue
Block a user