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:
@@ -1,10 +1,149 @@
|
||||
<script setup lang="ts">
|
||||
// TODO: Implement public-facing server information page
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Server, Users, Calendar, MessageCircle, Loader2, ExternalLink } from 'lucide-vue-next'
|
||||
|
||||
interface ServerInfo {
|
||||
server_name: string
|
||||
description: string | null
|
||||
header_image: string | null
|
||||
motd: string | null
|
||||
wipe_schedule: string | null
|
||||
discord_invite: string | null
|
||||
player_count: number
|
||||
max_players: number
|
||||
mods: string[]
|
||||
connect_url: string
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
const subdomain = route.params.subdomain as string
|
||||
const serverInfo = ref<ServerInfo | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
async function fetchServerInfo() {
|
||||
isLoading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const response = await fetch(`/api/public/${subdomain}/info`)
|
||||
if (!response.ok) {
|
||||
throw new Error('Server not found')
|
||||
}
|
||||
serverInfo.value = await response.json()
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to load server info'
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function copyConnectUrl() {
|
||||
if (serverInfo.value?.connect_url) {
|
||||
navigator.clipboard.writeText(serverInfo.value.connect_url)
|
||||
alert('Connect URL copied to clipboard')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchServerInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Server Info</h1>
|
||||
<p class="text-neutral-400">Public server information — rules, description, and connection details.</p>
|
||||
<div class="min-h-screen bg-neutral-950">
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="flex items-center justify-center min-h-screen">
|
||||
<Loader2 class="w-8 h-8 text-oxide-500 animate-spin" />
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="flex items-center justify-center min-h-screen p-6">
|
||||
<div class="bg-neutral-900 border border-neutral-800 rounded-lg p-8 max-w-md text-center">
|
||||
<Server class="w-12 h-12 text-neutral-600 mx-auto mb-4" />
|
||||
<h1 class="text-xl font-bold text-neutral-100 mb-2">Server Not Found</h1>
|
||||
<p class="text-sm text-neutral-400">{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Server Info -->
|
||||
<div v-else-if="serverInfo" class="max-w-4xl mx-auto p-6 space-y-6">
|
||||
<!-- Header Image -->
|
||||
<div v-if="serverInfo.header_image" class="rounded-lg overflow-hidden">
|
||||
<img :src="serverInfo.header_image" :alt="serverInfo.server_name" class="w-full h-64 object-cover" />
|
||||
</div>
|
||||
|
||||
<!-- Server Name & Stats -->
|
||||
<div class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-neutral-100 mb-2">{{ serverInfo.server_name }}</h1>
|
||||
<div class="flex items-center gap-2 text-neutral-400">
|
||||
<Users class="w-4 h-4" />
|
||||
<span class="text-sm">{{ serverInfo.player_count }}/{{ serverInfo.max_players }} players online</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="copyConnectUrl"
|
||||
class="flex items-center gap-2 px-4 py-2 bg-oxide-500 hover:bg-oxide-600 text-white font-medium rounded-lg transition-colors"
|
||||
>
|
||||
<ExternalLink class="w-4 h-4" />
|
||||
Connect
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="serverInfo.description" class="text-neutral-300 leading-relaxed">
|
||||
{{ serverInfo.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- MOTD -->
|
||||
<div v-if="serverInfo.motd" class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
|
||||
<h2 class="text-lg font-bold text-neutral-100 mb-3">Message of the Day</h2>
|
||||
<p class="text-neutral-300 whitespace-pre-line">{{ serverInfo.motd }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Wipe Schedule -->
|
||||
<div v-if="serverInfo.wipe_schedule" class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<Calendar class="w-5 h-5 text-oxide-500" />
|
||||
<h2 class="text-lg font-bold text-neutral-100">Wipe Schedule</h2>
|
||||
</div>
|
||||
<p class="text-neutral-300">{{ serverInfo.wipe_schedule }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Mods -->
|
||||
<div v-if="serverInfo.mods.length > 0" class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
|
||||
<h2 class="text-lg font-bold text-neutral-100 mb-3">Active Mods</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span
|
||||
v-for="mod in serverInfo.mods"
|
||||
:key="mod"
|
||||
class="px-3 py-1 bg-neutral-800 border border-neutral-700 rounded-full text-sm text-neutral-300"
|
||||
>
|
||||
{{ mod }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Discord -->
|
||||
<div v-if="serverInfo.discord_invite" class="bg-neutral-900 border border-neutral-800 rounded-lg p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<MessageCircle class="w-5 h-5 text-oxide-500" />
|
||||
<h2 class="text-lg font-bold text-neutral-100">Join our Discord</h2>
|
||||
</div>
|
||||
<a
|
||||
:href="serverInfo.discord_invite"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-2 px-4 py-2 bg-neutral-800 hover:bg-neutral-700 text-neutral-200 font-medium rounded-lg transition-colors"
|
||||
>
|
||||
<ExternalLink class="w-4 h-4" />
|
||||
Join
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user