All checks were successful
Test Asgard Runner / test (push) Successful in 3s
- Migration 021: raidablebases_configs table with JSONB config_data - Entity, module, controller (7 endpoints), service with NATS deploy/import - Frontend: 4-tab editor (General, Difficulty, NPC, Loot & Rewards) - Pinia store, types, router route, sidebar nav with Swords icon - Top 30 most common settings with actual RaidableBases.json key paths - Difficulty sub-tabs for Easy/Medium/Hard/Expert/Nightmare with spawn day toggles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
228 lines
8.3 KiB
Vue
228 lines
8.3 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { RouterView, RouterLink, useRoute, useRouter } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { useServerStore } from '@/stores/server'
|
|
import {
|
|
LayoutDashboard,
|
|
Server,
|
|
Terminal,
|
|
Users,
|
|
Puzzle,
|
|
RefreshCw,
|
|
Map,
|
|
MessageSquare,
|
|
BarChart3,
|
|
Bell,
|
|
UserPlus,
|
|
ShoppingBag,
|
|
Package,
|
|
Settings,
|
|
LogOut,
|
|
Shield,
|
|
Key,
|
|
CreditCard,
|
|
Network,
|
|
Clock,
|
|
AlertTriangle,
|
|
FileText,
|
|
FolderOpen,
|
|
Crosshair,
|
|
Navigation2,
|
|
Pickaxe,
|
|
DoorOpen,
|
|
Gift,
|
|
Flame,
|
|
Swords,
|
|
Menu,
|
|
X,
|
|
} from 'lucide-vue-next'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const auth = useAuthStore()
|
|
const server = useServerStore()
|
|
const sidebarOpen = ref(false)
|
|
|
|
const navItems = [
|
|
{ name: 'Dashboard', path: '/', icon: LayoutDashboard, permission: null },
|
|
{ name: 'Server', path: '/server', icon: Server, permission: 'server.view' },
|
|
{ name: 'Console', path: '/console', icon: Terminal, permission: 'console.view' },
|
|
{ name: 'Players', path: '/players', icon: Users, permission: 'players.view' },
|
|
{ name: 'Plugins', path: '/plugins', icon: Puzzle, permission: 'plugins.view' },
|
|
{ name: 'File Manager', path: '/files', icon: FolderOpen, permission: 'files.view' },
|
|
{ name: 'Loot Builder', path: '/loot-builder', icon: Crosshair, permission: 'loot.view' },
|
|
{ name: 'Teleport Config', path: '/teleport-config', icon: Navigation2, permission: 'teleport.view' },
|
|
{ name: 'Gather Rates', path: '/gather-manager', icon: Pickaxe, permission: 'gather.view' },
|
|
{ name: 'Auto Doors', path: '/autodoors', icon: DoorOpen, permission: 'autodoors.view' },
|
|
{ name: 'Kits', path: '/kits', icon: Gift, permission: 'kits.view' },
|
|
{ name: 'Furnace Splitter', path: '/furnace-splitter', icon: Flame, permission: 'furnacesplitter.view' },
|
|
{ name: 'Better Chat', path: '/better-chat', icon: MessageSquare, permission: 'betterchat.view' },
|
|
{ name: 'Timed Execute', path: '/timed-execute', icon: Clock, permission: 'timedexecute.view' },
|
|
{ name: 'Raidable Bases', path: '/raidable-bases', icon: Swords, permission: 'raidablebases.view' },
|
|
{ name: 'Auto-Wiper', path: '/wipes', icon: RefreshCw, permission: 'wipes.view' },
|
|
{ name: 'Maps', path: '/maps', icon: Map, permission: 'maps.view' },
|
|
{ name: 'Chat Log', path: '/chat', icon: MessageSquare, permission: 'chat.view' },
|
|
{ name: 'Analytics', path: '/analytics', icon: BarChart3, permission: 'analytics.view' },
|
|
{ name: 'Schedules', path: '/schedules', icon: Clock, permission: 'schedules.view' },
|
|
{ name: 'Alerts', path: '/alerts', icon: AlertTriangle, permission: 'alerts.view' },
|
|
{ name: 'Notifications', path: '/notifications', icon: Bell, permission: 'notifications.view' },
|
|
{ name: 'Team', path: '/team', icon: UserPlus, permission: null },
|
|
{ name: 'Store', path: '/store/config', icon: ShoppingBag, permission: 'store.view' },
|
|
{ name: 'Modules', path: '/modules', icon: Package, permission: 'modules.view' },
|
|
{ name: 'Changelog', path: '/changelog', icon: FileText, permission: 'changelog.view' },
|
|
{ name: 'Settings', path: '/settings', icon: Settings, permission: 'settings.view' },
|
|
]
|
|
|
|
const adminNavItems = [
|
|
{ name: 'Admin Home', path: '/admin', icon: Shield },
|
|
{ name: 'Licenses', path: '/admin/licenses', icon: Key },
|
|
{ name: 'Subscriptions', path: '/admin/subscriptions', icon: CreditCard },
|
|
{ name: 'Users', path: '/admin/users', icon: Users },
|
|
{ name: 'Server Fleet', path: '/admin/servers', icon: Network },
|
|
]
|
|
|
|
function isActive(path: string): boolean {
|
|
if (path === '/' || path === '/admin') return route.path === path
|
|
return route.path.startsWith(path)
|
|
}
|
|
|
|
function handleLogout() {
|
|
auth.logout()
|
|
router.push('/login')
|
|
}
|
|
|
|
function closeSidebar() {
|
|
sidebarOpen.value = false
|
|
}
|
|
|
|
function canShowNavItem(item: typeof navItems[0]): boolean {
|
|
if (!item.permission) return true
|
|
return auth.hasPermission(item.permission)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex h-screen bg-neutral-950">
|
|
<!-- Mobile Hamburger -->
|
|
<button
|
|
@click="sidebarOpen = true"
|
|
class="md:hidden fixed top-4 left-4 z-40 p-2 bg-neutral-900 border border-neutral-800 rounded-lg text-neutral-300 hover:text-oxide-400 transition-colors"
|
|
>
|
|
<Menu class="w-5 h-5" />
|
|
</button>
|
|
|
|
<!-- Sidebar Overlay (Mobile) -->
|
|
<div
|
|
v-if="sidebarOpen"
|
|
@click="closeSidebar"
|
|
class="md:hidden fixed inset-0 bg-black/50 z-40"
|
|
/>
|
|
|
|
<!-- Sidebar -->
|
|
<aside
|
|
class="w-64 bg-neutral-900 border-r border-neutral-800 flex flex-col fixed inset-y-0 left-0 z-50 transform transition-transform"
|
|
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'"
|
|
>
|
|
<!-- Logo -->
|
|
<div class="p-4 border-b border-neutral-800">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<img src="/logo.png" alt="Corrosion" class="h-8 w-8" />
|
|
<div>
|
|
<h1 class="text-sm font-bold text-oxide-500 tracking-wider">CORROSION</h1>
|
|
<p class="text-xs text-neutral-500">{{ auth.license?.server_name || 'Server Management' }}</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
@click="closeSidebar"
|
|
class="md:hidden text-neutral-400 hover:text-neutral-200 transition-colors"
|
|
>
|
|
<X class="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Server Status Indicator -->
|
|
<div class="px-4 py-3 border-b border-neutral-800">
|
|
<div class="flex items-center gap-2">
|
|
<div
|
|
class="w-2 h-2 rounded-full"
|
|
:class="{
|
|
'bg-green-500': server.connection?.connection_status === 'connected',
|
|
'bg-yellow-500': server.connection?.connection_status === 'degraded',
|
|
'bg-red-500': server.connection?.connection_status === 'offline' || !server.connection,
|
|
}"
|
|
/>
|
|
<span class="text-sm text-neutral-400">
|
|
{{ server.stats?.player_count ?? 0 }}/{{ server.stats?.max_players ?? 0 }} players
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<nav class="flex-1 overflow-y-auto py-2">
|
|
<RouterLink
|
|
v-for="item in navItems"
|
|
v-show="canShowNavItem(item)"
|
|
:key="item.path"
|
|
:to="item.path"
|
|
@click="closeSidebar"
|
|
class="flex items-center gap-3 px-4 py-2 mx-2 rounded-lg text-sm transition-colors"
|
|
:class="isActive(item.path)
|
|
? 'bg-oxide-500/10 text-oxide-400'
|
|
: 'text-neutral-400 hover:bg-neutral-800 hover:text-neutral-200'"
|
|
>
|
|
<component :is="item.icon" class="w-4 h-4" />
|
|
{{ item.name }}
|
|
</RouterLink>
|
|
|
|
<!-- Platform Admin Section (super-admin only) -->
|
|
<template v-if="auth.isSuperAdmin">
|
|
<div class="mt-4 mb-2 px-4">
|
|
<div class="flex items-center gap-2">
|
|
<div class="flex-1 border-t border-neutral-700" />
|
|
<span class="text-[10px] font-semibold uppercase tracking-widest text-oxide-500">Platform</span>
|
|
<div class="flex-1 border-t border-neutral-700" />
|
|
</div>
|
|
</div>
|
|
<RouterLink
|
|
v-for="item in adminNavItems"
|
|
:key="item.path"
|
|
:to="item.path"
|
|
@click="closeSidebar"
|
|
class="flex items-center gap-3 px-4 py-2 mx-2 rounded-lg text-sm transition-colors"
|
|
:class="isActive(item.path)
|
|
? 'bg-oxide-500/10 text-oxide-400'
|
|
: 'text-neutral-400 hover:bg-neutral-800 hover:text-neutral-200'"
|
|
>
|
|
<component :is="item.icon" class="w-4 h-4" />
|
|
{{ item.name }}
|
|
</RouterLink>
|
|
</template>
|
|
</nav>
|
|
|
|
<!-- User -->
|
|
<div class="p-4 border-t border-neutral-800">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-neutral-300">{{ auth.user?.username }}</p>
|
|
<p class="text-xs text-neutral-500">{{ auth.user?.email }}</p>
|
|
</div>
|
|
<button
|
|
@click="handleLogout"
|
|
class="text-neutral-500 hover:text-oxide-400 transition-colors"
|
|
>
|
|
<LogOut class="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content (offset by sidebar width on desktop) -->
|
|
<main class="flex-1 overflow-y-auto md:pl-64">
|
|
<RouterView />
|
|
</main>
|
|
</div>
|
|
</template>
|