feat: Waves 3+4 — frontend wiring, NATS integration, stores (19 files)
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:
Vantz Stockwell
2026-02-21 13:34:09 -05:00
parent a181ed7ded
commit 8bb6cc0890
19 changed files with 776 additions and 139 deletions

View File

@@ -1,26 +1,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { ref } from 'vue'
import { Shield, Users, Star, MessageCircle, Clock, ChevronRight, Check, Zap, Terminal, RefreshCw, LayoutDashboard } from 'lucide-vue-next'
// ---------- Countdown ----------
const targetDate = new Date('2026-02-28T12:00:00-05:00')
const now = ref(Date.now())
let timer: ReturnType<typeof setInterval>
const countdown = computed(() => {
const diff = Math.max(0, targetDate.getTime() - now.value)
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
const seconds = Math.floor((diff % (1000 * 60)) / 1000)
return { days, hours, minutes, seconds }
})
onMounted(() => {
timer = setInterval(() => { now.value = Date.now() }, 1000)
})
onUnmounted(() => clearInterval(timer))
// ---------- Email capture ----------
const email = ref('')
const serverCount = ref('')
@@ -92,19 +73,19 @@ const totalVotes = computed(() => voteItems.value.reduce((sum, i) => sum + i.vot
<!-- Hero -->
<section class="relative overflow-hidden">
<div class="max-w-4xl mx-auto px-6 pt-20 pb-16 text-center">
<span class="inline-block px-4 py-1.5 bg-oxide-500/10 border border-oxide-500/20 rounded-full text-oxide-400 text-sm font-medium mb-6">
Early Access Opening Soon
<span class="inline-block px-4 py-1.5 bg-green-500/10 border border-green-500/20 rounded-full text-green-400 text-sm font-medium mb-6">
Early Access Is Now Open
</span>
<h1 class="text-4xl md:text-5xl font-bold text-neutral-100 mb-4 tracking-tight">
Wipe Night Is About to<br />
<span class="text-oxide-500">Get Easier.</span>
Wipe Night Just Got<br />
<span class="text-oxide-500">A Lot Easier.</span>
</h1>
<p class="text-lg text-neutral-400 max-w-xl mx-auto mb-10">
Corrosion is entering limited early access. Install once. Automate everything. Never SSH again.
Corrosion is live in limited early access. Install once. Automate everything. Never SSH again.
</p>
<div class="flex items-center justify-center gap-4">
<a href="#join" class="px-8 py-3.5 bg-oxide-600 hover:bg-oxide-700 text-white font-semibold rounded-lg transition-colors">
Join Early Access
Claim Your Spot
</a>
<a href="#demo" class="px-8 py-3.5 bg-neutral-800 hover:bg-neutral-700 text-neutral-200 font-semibold rounded-lg border border-neutral-700 transition-colors">
View Demo Architecture
@@ -114,39 +95,16 @@ const totalVotes = computed(() => voteItems.value.reduce((sum, i) => sum + i.vot
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[400px] bg-oxide-500/8 rounded-full blur-3xl pointer-events-none" />
</section>
<!-- Countdown -->
<!-- Early Access Live Banner -->
<section class="py-12 border-t border-neutral-800">
<div class="max-w-3xl mx-auto px-6 text-center">
<p class="text-sm text-neutral-500 uppercase tracking-wider mb-6">Early Access Opens In</p>
<div class="flex items-center justify-center gap-4 md:gap-6">
<div class="text-center">
<div class="w-20 h-20 bg-neutral-900 border border-neutral-800 rounded-xl flex items-center justify-center">
<span class="text-3xl font-bold text-oxide-400 tabular-nums">{{ countdown.days }}</span>
</div>
<p class="text-xs text-neutral-500 mt-2">Days</p>
</div>
<span class="text-2xl text-neutral-700 font-light">:</span>
<div class="text-center">
<div class="w-20 h-20 bg-neutral-900 border border-neutral-800 rounded-xl flex items-center justify-center">
<span class="text-3xl font-bold text-oxide-400 tabular-nums">{{ String(countdown.hours).padStart(2, '0') }}</span>
</div>
<p class="text-xs text-neutral-500 mt-2">Hours</p>
</div>
<span class="text-2xl text-neutral-700 font-light">:</span>
<div class="text-center">
<div class="w-20 h-20 bg-neutral-900 border border-neutral-800 rounded-xl flex items-center justify-center">
<span class="text-3xl font-bold text-neutral-200 tabular-nums">{{ String(countdown.minutes).padStart(2, '0') }}</span>
</div>
<p class="text-xs text-neutral-500 mt-2">Minutes</p>
</div>
<span class="text-2xl text-neutral-700 font-light">:</span>
<div class="text-center">
<div class="w-20 h-20 bg-neutral-900 border border-neutral-800 rounded-xl flex items-center justify-center">
<span class="text-3xl font-bold text-neutral-200 tabular-nums">{{ String(countdown.seconds).padStart(2, '0') }}</span>
</div>
<p class="text-xs text-neutral-500 mt-2">Seconds</p>
</div>
<div class="inline-flex items-center gap-3 px-6 py-4 bg-green-500/10 border border-green-500/20 rounded-2xl">
<div class="w-2.5 h-2.5 bg-green-400 rounded-full animate-pulse shrink-0" />
<p class="text-green-300 font-semibold text-lg">Early Access is now live founding admin spots are limited.</p>
</div>
<p class="text-neutral-500 text-sm mt-4">
Sign up below to lock in founding pricing before spots run out.
</p>
</div>
</section>
@@ -182,13 +140,13 @@ const totalVotes = computed(() => voteItems.value.reduce((sum, i) => sum + i.vot
<!-- Email Capture -->
<section id="join" class="py-16 border-t border-neutral-800">
<div class="max-w-md mx-auto px-6">
<h2 class="text-2xl font-bold text-neutral-100 mb-2 text-center">Get on the List</h2>
<p class="text-neutral-400 text-center mb-8">Be first to know when early access opens.</p>
<h2 class="text-2xl font-bold text-neutral-100 mb-2 text-center">Claim Your Founding Spot</h2>
<p class="text-neutral-400 text-center mb-8">Early access is open now. Spots are limited lock in founding pricing today.</p>
<div v-if="submitted" class="bg-green-500/10 border border-green-500/20 rounded-xl p-8 text-center">
<Check class="w-10 h-10 text-green-400 mx-auto mb-3" />
<h3 class="text-lg font-semibold text-neutral-100 mb-1">You're on the list.</h3>
<p class="text-sm text-neutral-400">We'll reach out when early access opens.</p>
<h3 class="text-lg font-semibold text-neutral-100 mb-1">You're in.</h3>
<p class="text-sm text-neutral-400">We'll be in touch shortly with your access details.</p>
</div>
<form v-else @submit.prevent="handleSubmit" class="space-y-4">
@@ -341,12 +299,12 @@ const totalVotes = computed(() => voteItems.value.reduce((sum, i) => sum + i.vot
</div>
<div class="ml-4 w-px h-4 bg-neutral-800" />
<div class="flex items-start gap-4">
<div class="w-8 h-8 bg-oxide-500/10 border border-oxide-500/20 rounded-full flex items-center justify-center shrink-0 mt-0.5">
<ChevronRight class="w-4 h-4 text-oxide-400" />
<div class="w-8 h-8 bg-green-500/10 border border-green-500/20 rounded-full flex items-center justify-center shrink-0 mt-0.5">
<Check class="w-4 h-4 text-green-400" />
</div>
<div>
<p class="text-sm font-medium text-oxide-400">Week 2 Early Access Opens</p>
<p class="text-xs text-neutral-500 mt-0.5">Founding Admin licenses go live.</p>
<p class="text-sm font-medium text-neutral-200">Week 2 Early Access Open</p>
<p class="text-xs text-neutral-500 mt-0.5">Founding Admin licenses are live claim yours now.</p>
</div>
</div>
<div class="ml-4 w-px h-4 bg-neutral-800" />