WP-11: package and OS updates from the UI
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / ui (push) Has been cancelled

package_upgrade job streams apt-get output into the job log (streaming
CommandRunner, DebianUpdater with dist-upgrade or --only-upgrade), refreshes
the inventory afterwards and flags reboot-required. POST /api/system/upgrade
(admin), package name validation, selectable package table with confirm
dialog and live log on the Updates page. Fake updater for dev.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-09-02 22:27:28 +02:00
parent 1f56f015a2
commit 684695e71a
9 changed files with 277 additions and 33 deletions

View File

@ -2,8 +2,16 @@
import { computed, ref } from 'vue'
import type { Package } from '../api/types'
const props = defineProps<{ packages: Package[] }>()
const props = defineProps<{ packages: Package[]; selectable?: boolean }>()
const emit = defineEmits<{ 'update:selected': [names: string[]] }>()
const selected = ref<Set<string>>(new Set())
const search = ref('')
function toggle(name: string, on: boolean) {
if (on) selected.value.add(name)
else selected.value.delete(name)
emit('update:selected', [...selected.value])
}
const upgradableOnly = ref(false)
const source = ref<'all' | 'apt' | 'snap'>('all')
@ -52,6 +60,7 @@ const filtered = computed(() =>
<table class="mt-4 w-full text-left text-sm">
<thead class="border-b border-gray-200 text-gray-500">
<tr>
<th v-if="selectable" class="w-8"></th>
<th class="py-2">Package</th>
<th>Source</th>
<th>Installed</th>
@ -66,6 +75,16 @@ const filtered = computed(() =>
class="border-b border-gray-100"
:class="p.candidate ? 'bg-amber-50' : ''"
>
<td v-if="selectable">
<input
v-if="p.candidate"
name="select"
type="checkbox"
:aria-label="`Select ${p.name}`"
:checked="selected.has(p.name)"
@change="toggle(p.name, ($event.target as HTMLInputElement).checked)"
/>
</td>
<td class="py-1.5 font-mono">{{ p.name }}</td>
<td>{{ p.source }}</td>
<td class="font-mono text-gray-600">{{ p.installed }}</td>
@ -79,7 +98,9 @@ const filtered = computed(() =>
</td>
</tr>
<tr v-if="filtered.length === 0">
<td colspan="5" class="py-6 text-center text-gray-500">No packages match.</td>
<td :colspan="selectable ? 6 : 5" class="py-6 text-center text-gray-500">
No packages match.
</td>
</tr>
</tbody>
</table>