158 lines
5.5 KiB
Vue
158 lines
5.5 KiB
Vue
<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>
|