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:
102
frontend/src/views/auth/ForgotPasswordView.vue
Normal file
102
frontend/src/views/auth/ForgotPasswordView.vue
Normal 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>
|
||||
Reference in New Issue
Block a user