feat: Frontend gap closure — Schedules, Alerts, Migration, Changelog views

Implements missing frontend views and API integrations:

New Views:
- SchedulesView: CRUD for scheduled tasks (restart/announcement/command/plugin_reload)
- MigrationView: Export/import interface with file upload and history tracking
- ChangelogView: Paginated changelog feed with category badges
- ForgotPasswordView: Password reset flow with email submission
- AlertsView: Alert config dashboard with threshold settings and history

Component Updates:
- ErrorBoundary: Global error handler with retry functionality
- DashboardLayout: Mobile responsive sidebar, permission-based nav, new menu items
- ServerInfoView: Complete rewrite for public server info display

Infrastructure:
- useApi: Token refresh interceptor with 401 retry and infinite loop prevention
- plugins store: Implemented all stubbed methods with real API calls
- auth store: Added hasPermission() helper for RBAC UI visibility
- Router: Added new routes with catch-all fallback

Purpose: Closes frontend implementation gaps. Hardens auth flow, improves mobile UX,
enables server automation scheduling, alert configuration, and data migration tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-15 21:20:40 -05:00
parent 8cd792eb75
commit 4c648783a2
14 changed files with 1327 additions and 40 deletions

View File

@@ -0,0 +1,102 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useApi } from '@/composables/useApi'
import { RouterLink } from 'vue-router'
import { Mail, ArrowLeft, CheckCircle2, Loader2 } from 'lucide-vue-next'
const api = useApi()
const email = ref('')
const isLoading = ref(false)
const success = ref(false)
const errorMessage = ref('')
async function handleSubmit() {
if (!email.value) return
isLoading.value = true
errorMessage.value = ''
try {
await api.post('/auth/forgot-password', { email: email.value })
success.value = true
} catch (err) {
errorMessage.value = err instanceof Error ? err.message : 'Failed to send reset email'
} finally {
isLoading.value = false
}
}
</script>
<template>
<div class="min-h-screen bg-neutral-950 flex items-center justify-center p-6">
<div class="w-full max-w-md">
<!-- Logo -->
<div class="text-center mb-8">
<div class="flex items-center justify-center gap-2 mb-2">
<img src="/logo.png" alt="Corrosion" class="h-10 w-10" />
<h1 class="text-2xl font-bold text-oxide-500 tracking-wider">CORROSION</h1>
</div>
<p class="text-neutral-500 text-sm">Server Management Platform</p>
</div>
<!-- Card -->
<div class="bg-neutral-900 border border-neutral-800 rounded-lg p-8">
<div v-if="success" class="text-center space-y-4">
<CheckCircle2 class="w-12 h-12 text-green-500 mx-auto" />
<h2 class="text-xl font-bold text-neutral-100">Check your email</h2>
<p class="text-sm text-neutral-400">
We've sent password reset instructions to <strong class="text-neutral-200">{{ email }}</strong>
</p>
<RouterLink
to="/login"
class="inline-flex items-center gap-2 text-sm text-oxide-400 hover:text-oxide-300 transition-colors"
>
<ArrowLeft class="w-4 h-4" />
Back to login
</RouterLink>
</div>
<form v-else @submit.prevent="handleSubmit" class="space-y-6">
<div class="text-center mb-6">
<h2 class="text-xl font-bold text-neutral-100 mb-2">Forgot password?</h2>
<p class="text-sm text-neutral-400">Enter your email to receive reset instructions</p>
</div>
<div>
<label class="block text-sm text-neutral-400 mb-2">Email</label>
<div class="relative">
<Mail class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-neutral-500" />
<input
v-model="email"
type="email"
required
class="w-full pl-10 pr-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-neutral-200 placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-oxide-500"
placeholder="admin@example.com"
/>
</div>
</div>
<div v-if="errorMessage" class="p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
<p class="text-sm text-red-400">{{ errorMessage }}</p>
</div>
<button
type="submit"
:disabled="isLoading"
class="w-full flex items-center justify-center gap-2 py-2 bg-oxide-500 hover:bg-oxide-600 disabled:opacity-50 text-white font-medium rounded-lg transition-colors"
>
<Loader2 v-if="isLoading" class="w-5 h-5 animate-spin" />
<span>{{ isLoading ? 'Sending...' : 'Send Reset Link' }}</span>
</button>
<RouterLink
to="/login"
class="flex items-center justify-center gap-2 text-sm text-neutral-400 hover:text-neutral-200 transition-colors"
>
<ArrowLeft class="w-4 h-4" />
Back to login
</RouterLink>
</form>
</div>
</div>
</div>
</template>