feat: Waves 3+4 — frontend wiring, NATS integration, stores (19 files)
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
Frontend:
- Wire Dashboard quick actions (start/stop/trigger wipe) + next wipe schedule
- Wire Console WebSocket streaming for real-time output
- Implement TOTP 2FA challenge flow in LoginView
- Wire Plugin load/unload toggle + uninstall buttons with confirmations
- Wire WipesView profile selector, disable trigger when no profiles
- Build full WipeProfiles create/edit modal with all config fields
- Wire MapsView file upload with multipart FormData
- Fix SettingsView empty catch blocks → toast error messages
- Fix stale localStorage token reads in CSV exports → auth store
- Fix auth store hardcoded permissions → JWT-decoded role permissions
- Fix wipe store onMounted lifecycle bug → explicit subscribe action
- Update EarlyAccessView from countdown to "Now Live" state
Backend:
- Wire wipe trigger to publish NATS cmd (corrosion.{id}.cmd.wipe)
- Wire plugin reload/uninstall to publish NATS cmd
- Expand NatsBridgeService: add files, wipe status, server status subs
- Add PATCH schedules/:id/toggle endpoint for task toggling
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,14 +2,17 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useWipeStore } from '@/stores/wipe'
|
||||
import { useServerStore } from '@/stores/server'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { RefreshCw, Zap, Clock, AlertTriangle, Loader2 } from 'lucide-vue-next'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { safeDate } from '@/utils/formatters'
|
||||
|
||||
const wipeStore = useWipeStore()
|
||||
const server = useServerStore()
|
||||
const toast = useToastStore()
|
||||
|
||||
const triggerType = ref<'map' | 'blueprint' | 'full'>('map')
|
||||
const selectedProfileId = ref<string>('')
|
||||
const triggerLoading = ref(false)
|
||||
const dryRunLoading = ref(false)
|
||||
|
||||
@@ -17,9 +20,9 @@ async function triggerWipe() {
|
||||
if (!confirm(`Trigger a ${triggerType.value} wipe? This cannot be undone.`)) return
|
||||
triggerLoading.value = true
|
||||
try {
|
||||
await wipeStore.triggerWipe(triggerType.value, '')
|
||||
} catch {
|
||||
// Handle error
|
||||
await wipeStore.triggerWipe(triggerType.value, selectedProfileId.value)
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : 'Failed to trigger wipe')
|
||||
} finally {
|
||||
triggerLoading.value = false
|
||||
}
|
||||
@@ -28,15 +31,19 @@ async function triggerWipe() {
|
||||
async function triggerDryRun() {
|
||||
dryRunLoading.value = true
|
||||
try {
|
||||
await wipeStore.triggerDryRun(triggerType.value, '')
|
||||
} catch {
|
||||
// Handle error
|
||||
await wipeStore.triggerDryRun(triggerType.value, selectedProfileId.value)
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : 'Failed to run dry-run')
|
||||
} finally {
|
||||
dryRunLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
await wipeStore.fetchProfiles()
|
||||
if (wipeStore.profiles.length > 0 && wipeStore.profiles[0]) {
|
||||
selectedProfileId.value = wipeStore.profiles[0].id
|
||||
}
|
||||
wipeStore.fetchSchedules()
|
||||
wipeStore.fetchHistory()
|
||||
})
|
||||
@@ -75,6 +82,10 @@ onMounted(() => {
|
||||
<!-- Manual Trigger -->
|
||||
<div class="bg-neutral-900 border border-neutral-800 rounded-lg p-5">
|
||||
<h2 class="text-sm font-medium text-neutral-400 uppercase tracking-wider mb-4">Manual Wipe</h2>
|
||||
<div v-if="wipeStore.profiles.length === 0" class="mb-4 flex items-center gap-2 text-sm text-yellow-400 bg-yellow-500/10 border border-yellow-500/20 rounded-lg px-4 py-2">
|
||||
<AlertTriangle class="w-4 h-4 shrink-0" />
|
||||
No wipe profiles found. <RouterLink to="/wipes/profiles" class="underline hover:text-yellow-300 ml-1">Create a profile</RouterLink> before triggering a wipe.
|
||||
</div>
|
||||
<div class="flex items-end gap-4">
|
||||
<div>
|
||||
<label class="block text-xs text-neutral-500 mb-2">Wipe Type</label>
|
||||
@@ -90,9 +101,22 @@ onMounted(() => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-neutral-500 mb-2">Profile</label>
|
||||
<select
|
||||
v-model="selectedProfileId"
|
||||
:disabled="wipeStore.profiles.length === 0"
|
||||
class="px-3 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-100 focus:outline-none focus:ring-2 focus:ring-oxide-500/50 focus:border-oxide-500 transition-colors disabled:opacity-50"
|
||||
>
|
||||
<option value="">No profile</option>
|
||||
<option v-for="profile in wipeStore.profiles" :key="profile.id" :value="profile.id">
|
||||
{{ profile.profile_name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
@click="triggerDryRun"
|
||||
:disabled="dryRunLoading"
|
||||
:disabled="dryRunLoading || wipeStore.profiles.length === 0"
|
||||
class="flex items-center gap-2 px-4 py-2 text-sm font-medium text-neutral-300 bg-neutral-800 hover:bg-neutral-700 disabled:opacity-50 border border-neutral-700 rounded-lg transition-colors"
|
||||
>
|
||||
<Loader2 v-if="dryRunLoading" class="w-4 h-4 animate-spin" />
|
||||
@@ -101,7 +125,7 @@ onMounted(() => {
|
||||
</button>
|
||||
<button
|
||||
@click="triggerWipe"
|
||||
:disabled="triggerLoading || server.connection?.connection_status !== 'connected'"
|
||||
:disabled="triggerLoading || wipeStore.profiles.length === 0 || server.connection?.connection_status !== 'connected'"
|
||||
class="flex items-center gap-2 px-4 py-2 text-sm font-medium bg-red-600 hover:bg-red-700 disabled:opacity-40 disabled:cursor-not-allowed text-white rounded-lg transition-colors"
|
||||
>
|
||||
<Loader2 v-if="triggerLoading" class="w-4 h-4 animate-spin" />
|
||||
|
||||
Reference in New Issue
Block a user