scaffold: Vue 3 frontend — router, stores, views, composables, layouts
Complete frontend skeleton: Vite + Vue 3 + TypeScript + Tailwind CSS, Pinia stores (auth, server, wipe, plugins), authenticated API composable, full route tree with auth guards, DashboardLayout with sidebar nav, 23 view stubs across auth/admin/public, all TypeScript interfaces. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
5
frontend/README.md
Normal file
5
frontend/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Vue 3 + TypeScript + Vite
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>frontend</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2150
frontend/package-lock.json
generated
Normal file
2150
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
frontend/package.json
Normal file
25
frontend/package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
|
"vue": "^3.5.25"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.2",
|
||||||
|
"@vue/tsconfig": "^0.8.1",
|
||||||
|
"tailwindcss": "^4.1.18",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"vite": "^7.3.1",
|
||||||
|
"vue-tsc": "^3.1.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
frontend/public/vite.svg
Normal file
1
frontend/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
7
frontend/src/App.vue
Normal file
7
frontend/src/App.vue
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
1
frontend/src/assets/vue.svg
Normal file
1
frontend/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
119
frontend/src/components/layout/DashboardLayout.vue
Normal file
119
frontend/src/components/layout/DashboardLayout.vue
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterView, RouterLink, useRoute } 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,
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const server = useServerStore()
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ name: 'Dashboard', path: '/', icon: LayoutDashboard },
|
||||||
|
{ name: 'Server', path: '/server', icon: Server },
|
||||||
|
{ name: 'Console', path: '/console', icon: Terminal },
|
||||||
|
{ name: 'Players', path: '/players', icon: Users },
|
||||||
|
{ name: 'Plugins', path: '/plugins', icon: Puzzle },
|
||||||
|
{ name: 'Auto-Wiper', path: '/wipes', icon: RefreshCw },
|
||||||
|
{ name: 'Maps', path: '/maps', icon: Map },
|
||||||
|
{ name: 'Chat Log', path: '/chat', icon: MessageSquare },
|
||||||
|
{ name: 'Analytics', path: '/analytics', icon: BarChart3 },
|
||||||
|
{ name: 'Notifications', path: '/notifications', icon: Bell },
|
||||||
|
{ name: 'Team', path: '/team', icon: UserPlus },
|
||||||
|
{ name: 'Store', path: '/store/manage', icon: ShoppingBag },
|
||||||
|
{ name: 'Modules', path: '/modules', icon: Package },
|
||||||
|
{ name: 'Settings', path: '/settings', icon: Settings },
|
||||||
|
]
|
||||||
|
|
||||||
|
function isActive(path: string): boolean {
|
||||||
|
if (path === '/') return route.path === '/'
|
||||||
|
return route.path.startsWith(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogout() {
|
||||||
|
auth.logout()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex h-screen bg-neutral-950">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<aside class="w-64 bg-neutral-900 border-r border-neutral-800 flex flex-col">
|
||||||
|
<!-- Logo -->
|
||||||
|
<div class="p-4 border-b border-neutral-800">
|
||||||
|
<h1 class="text-xl font-bold text-red-500 tracking-wider">CORROSION</h1>
|
||||||
|
<p class="text-xs text-neutral-500 mt-1">{{ auth.license?.server_name || 'Server Management' }}</p>
|
||||||
|
</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"
|
||||||
|
:key="item.path"
|
||||||
|
:to="item.path"
|
||||||
|
class="flex items-center gap-3 px-4 py-2 mx-2 rounded-lg text-sm transition-colors"
|
||||||
|
:class="isActive(item.path)
|
||||||
|
? 'bg-red-500/10 text-red-400'
|
||||||
|
: 'text-neutral-400 hover:bg-neutral-800 hover:text-neutral-200'"
|
||||||
|
>
|
||||||
|
<component :is="item.icon" class="w-4 h-4" />
|
||||||
|
{{ item.name }}
|
||||||
|
</RouterLink>
|
||||||
|
</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-red-400 transition-colors"
|
||||||
|
>
|
||||||
|
<LogOut class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="flex-1 overflow-y-auto">
|
||||||
|
<RouterView />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
13
frontend/src/components/layout/PublicLayout.vue
Normal file
13
frontend/src/components/layout/PublicLayout.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-neutral-950">
|
||||||
|
<RouterView />
|
||||||
|
|
||||||
|
<footer class="py-6 text-center text-neutral-600 text-sm border-t border-neutral-800">
|
||||||
|
Powered by <span class="text-red-500 font-semibold">Corrosion</span>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
66
frontend/src/composables/useApi.ts
Normal file
66
frontend/src/composables/useApi.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
|
const API_BASE = '/api'
|
||||||
|
|
||||||
|
interface RequestOptions {
|
||||||
|
method?: string
|
||||||
|
body?: unknown
|
||||||
|
headers?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable for making authenticated API requests.
|
||||||
|
* Automatically attaches JWT token and handles token refresh.
|
||||||
|
*/
|
||||||
|
export function useApi() {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...options.headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.accessToken) {
|
||||||
|
headers['Authorization'] = `Bearer ${auth.accessToken}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE}${path}`, {
|
||||||
|
method: options.method || 'GET',
|
||||||
|
headers,
|
||||||
|
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
// TODO: Attempt token refresh, retry, or redirect to login
|
||||||
|
auth.logout()
|
||||||
|
window.location.href = '/login'
|
||||||
|
throw new Error('Unauthorized')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json().catch(() => ({ message: 'Request failed' }))
|
||||||
|
throw new Error(error.message || `HTTP ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
function get<T>(path: string) {
|
||||||
|
return request<T>(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
function post<T>(path: string, body?: unknown) {
|
||||||
|
return request<T>(path, { method: 'POST', body })
|
||||||
|
}
|
||||||
|
|
||||||
|
function put<T>(path: string, body?: unknown) {
|
||||||
|
return request<T>(path, { method: 'PUT', body })
|
||||||
|
}
|
||||||
|
|
||||||
|
function del<T>(path: string) {
|
||||||
|
return request<T>(path, { method: 'DELETE' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return { request, get, post, put, del }
|
||||||
|
}
|
||||||
17
frontend/src/main.ts
Normal file
17
frontend/src/main.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import './style.css'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
pinia.use(piniaPluginPersistedstate)
|
||||||
|
|
||||||
|
app.use(pinia)
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
163
frontend/src/router/index.ts
Normal file
163
frontend/src/router/index.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
// Auth routes (no layout)
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('@/views/auth/LoginView.vue'),
|
||||||
|
meta: { guest: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
name: 'register',
|
||||||
|
component: () => import('@/views/auth/RegisterView.vue'),
|
||||||
|
meta: { guest: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/setup',
|
||||||
|
name: 'setup-wizard',
|
||||||
|
component: () => import('@/views/auth/SetupWizardView.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Admin dashboard routes (with sidebar layout)
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('@/components/layout/DashboardLayout.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'dashboard',
|
||||||
|
component: () => import('@/views/admin/DashboardView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'server',
|
||||||
|
name: 'server',
|
||||||
|
component: () => import('@/views/admin/ServerView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'console',
|
||||||
|
name: 'console',
|
||||||
|
component: () => import('@/views/admin/ConsoleView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'players',
|
||||||
|
name: 'players',
|
||||||
|
component: () => import('@/views/admin/PlayersView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'plugins',
|
||||||
|
name: 'plugins',
|
||||||
|
component: () => import('@/views/admin/PluginsView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'wipes',
|
||||||
|
name: 'wipes',
|
||||||
|
component: () => import('@/views/admin/WipesView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'wipes/profiles',
|
||||||
|
name: 'wipe-profiles',
|
||||||
|
component: () => import('@/views/admin/WipeProfilesView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'wipes/calendar',
|
||||||
|
name: 'wipe-calendar',
|
||||||
|
component: () => import('@/views/admin/WipeCalendarView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'wipes/history',
|
||||||
|
name: 'wipe-history',
|
||||||
|
component: () => import('@/views/admin/WipeHistoryView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'maps',
|
||||||
|
name: 'maps',
|
||||||
|
component: () => import('@/views/admin/MapsView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'chat',
|
||||||
|
name: 'chat',
|
||||||
|
component: () => import('@/views/admin/ChatLogView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'analytics',
|
||||||
|
name: 'analytics',
|
||||||
|
component: () => import('@/views/admin/AnalyticsView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'notifications',
|
||||||
|
name: 'notifications',
|
||||||
|
component: () => import('@/views/admin/NotificationsView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'team',
|
||||||
|
name: 'team',
|
||||||
|
component: () => import('@/views/admin/TeamView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'store/manage',
|
||||||
|
name: 'store-manage',
|
||||||
|
component: () => import('@/views/admin/StoreManageView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'modules',
|
||||||
|
name: 'modules',
|
||||||
|
component: () => import('@/views/admin/ModuleStoreView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'settings',
|
||||||
|
name: 'settings',
|
||||||
|
component: () => import('@/views/admin/SettingsView.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Public server site routes (different layout)
|
||||||
|
{
|
||||||
|
path: '/s/:subdomain',
|
||||||
|
component: () => import('@/components/layout/PublicLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'public-server',
|
||||||
|
component: () => import('@/views/public/ServerInfoView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'store',
|
||||||
|
name: 'public-store',
|
||||||
|
component: () => import('@/views/public/StoreView.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Status page
|
||||||
|
{
|
||||||
|
path: '/status',
|
||||||
|
name: 'status',
|
||||||
|
component: () => import('@/views/public/StatusPageView.vue'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Auth guard
|
||||||
|
router.beforeEach((to, _from, next) => {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth && !auth.isAuthenticated) {
|
||||||
|
next({ name: 'login', query: { redirect: to.fullPath } })
|
||||||
|
} else if (to.meta.guest && auth.isAuthenticated) {
|
||||||
|
next({ name: 'dashboard' })
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
53
frontend/src/stores/auth.ts
Normal file
53
frontend/src/stores/auth.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import type { User, License } from '@/types'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
const user = ref<User | null>(null)
|
||||||
|
const license = ref<License | null>(null)
|
||||||
|
const accessToken = ref<string | null>(null)
|
||||||
|
const refreshToken = ref<string | null>(null)
|
||||||
|
|
||||||
|
const isAuthenticated = computed(() => !!accessToken.value)
|
||||||
|
const hasLicense = computed(() => !!license.value)
|
||||||
|
const isLicenseActive = computed(() => license.value?.status === 'active')
|
||||||
|
|
||||||
|
function setAuth(data: { access_token: string; refresh_token: string; user: User }) {
|
||||||
|
accessToken.value = data.access_token
|
||||||
|
refreshToken.value = data.refresh_token
|
||||||
|
user.value = data.user
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLicense(data: License) {
|
||||||
|
license.value = data
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
user.value = null
|
||||||
|
license.value = null
|
||||||
|
accessToken.value = null
|
||||||
|
refreshToken.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasModule(moduleSlug: string): boolean {
|
||||||
|
return license.value?.modules_enabled?.includes(moduleSlug) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
license,
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
isAuthenticated,
|
||||||
|
hasLicense,
|
||||||
|
isLicenseActive,
|
||||||
|
setAuth,
|
||||||
|
setLicense,
|
||||||
|
logout,
|
||||||
|
hasModule,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
persist: {
|
||||||
|
pick: ['accessToken', 'refreshToken', 'user', 'license'],
|
||||||
|
},
|
||||||
|
})
|
||||||
33
frontend/src/stores/plugins.ts
Normal file
33
frontend/src/stores/plugins.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { PluginEntry } from '@/types'
|
||||||
|
|
||||||
|
export const usePluginStore = defineStore('plugins', () => {
|
||||||
|
const plugins = ref<PluginEntry[]>([])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
async function fetchPlugins() {
|
||||||
|
// TODO: GET /api/plugins
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installPlugin(slug: string) {
|
||||||
|
// TODO: POST /api/plugins/install
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadPlugin(pluginId: string) {
|
||||||
|
// TODO: POST /api/plugins/:id/reload
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchUmod(query: string) {
|
||||||
|
// TODO: GET /api/plugins/search?q=query
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugins,
|
||||||
|
isLoading,
|
||||||
|
fetchPlugins,
|
||||||
|
installPlugin,
|
||||||
|
reloadPlugin,
|
||||||
|
searchUmod,
|
||||||
|
}
|
||||||
|
})
|
||||||
52
frontend/src/stores/server.ts
Normal file
52
frontend/src/stores/server.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { ServerConnection, ServerConfig, ServerStats } from '@/types'
|
||||||
|
|
||||||
|
export const useServerStore = defineStore('server', () => {
|
||||||
|
const connection = ref<ServerConnection | null>(null)
|
||||||
|
const config = ref<ServerConfig | null>(null)
|
||||||
|
const stats = ref<ServerStats | null>(null)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
async function fetchServerStatus() {
|
||||||
|
// TODO: Fetch from API
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchServerConfig() {
|
||||||
|
// TODO: Fetch from API
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startServer() {
|
||||||
|
// TODO: POST /api/servers/:id/start
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopServer() {
|
||||||
|
// TODO: POST /api/servers/:id/stop
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restartServer() {
|
||||||
|
// TODO: POST /api/servers/:id/restart
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendCommand(command: string) {
|
||||||
|
// TODO: POST /api/servers/:id/command
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStats(newStats: ServerStats) {
|
||||||
|
stats.value = newStats
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
connection,
|
||||||
|
config,
|
||||||
|
stats,
|
||||||
|
isLoading,
|
||||||
|
fetchServerStatus,
|
||||||
|
fetchServerConfig,
|
||||||
|
startServer,
|
||||||
|
stopServer,
|
||||||
|
restartServer,
|
||||||
|
sendCommand,
|
||||||
|
updateStats,
|
||||||
|
}
|
||||||
|
})
|
||||||
42
frontend/src/stores/wipe.ts
Normal file
42
frontend/src/stores/wipe.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { WipeProfile, WipeSchedule, WipeHistory } from '@/types'
|
||||||
|
|
||||||
|
export const useWipeStore = defineStore('wipe', () => {
|
||||||
|
const profiles = ref<WipeProfile[]>([])
|
||||||
|
const schedules = ref<WipeSchedule[]>([])
|
||||||
|
const history = ref<WipeHistory[]>([])
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
async function fetchProfiles() {
|
||||||
|
// TODO: GET /api/profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchSchedules() {
|
||||||
|
// TODO: GET /api/schedules
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchHistory() {
|
||||||
|
// TODO: GET /api/wipes/history
|
||||||
|
}
|
||||||
|
|
||||||
|
async function triggerWipe(wipeType: string, profileId: string) {
|
||||||
|
// TODO: POST /api/wipes/:server_id/trigger
|
||||||
|
}
|
||||||
|
|
||||||
|
async function triggerDryRun(wipeType: string, profileId: string) {
|
||||||
|
// TODO: POST /api/wipes/:server_id/dry-run
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
profiles,
|
||||||
|
schedules,
|
||||||
|
history,
|
||||||
|
isLoading,
|
||||||
|
fetchProfiles,
|
||||||
|
fetchSchedules,
|
||||||
|
fetchHistory,
|
||||||
|
triggerWipe,
|
||||||
|
triggerDryRun,
|
||||||
|
}
|
||||||
|
})
|
||||||
22
frontend/src/style.css
Normal file
22
frontend/src/style.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* Corrosion Platform — Custom Styles */
|
||||||
|
|
||||||
|
/* Dark mode is default — Rust servers run at night */
|
||||||
|
:root {
|
||||||
|
--corrosion-red: #ef4444;
|
||||||
|
--corrosion-orange: #f97316;
|
||||||
|
--corrosion-dark: #0f0f0f;
|
||||||
|
--corrosion-surface: #1a1a1a;
|
||||||
|
--corrosion-border: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-neutral-950 text-neutral-100 antialiased;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
225
frontend/src/types/index.ts
Normal file
225
frontend/src/types/index.ts
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
// Corrosion Platform — TypeScript Interfaces
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
username: string
|
||||||
|
totp_enabled: boolean
|
||||||
|
email_verified: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface License {
|
||||||
|
id: string
|
||||||
|
license_key: string
|
||||||
|
status: 'active' | 'suspended' | 'expired' | 'revoked'
|
||||||
|
server_name: string | null
|
||||||
|
subdomain: string | null
|
||||||
|
custom_domain: string | null
|
||||||
|
modules_enabled: string[]
|
||||||
|
webstore_active: boolean
|
||||||
|
created_at: string
|
||||||
|
expires_at: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthResponse {
|
||||||
|
access_token: string
|
||||||
|
refresh_token: string
|
||||||
|
requires_totp: boolean
|
||||||
|
user: User
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerConnection {
|
||||||
|
id: string
|
||||||
|
license_id: string
|
||||||
|
connection_type: 'amp' | 'pterodactyl' | 'bare_metal'
|
||||||
|
server_ip: string | null
|
||||||
|
server_port: number | null
|
||||||
|
game_port: number | null
|
||||||
|
connection_status: 'connected' | 'degraded' | 'offline'
|
||||||
|
plugin_last_seen: string | null
|
||||||
|
companion_last_seen: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerConfig {
|
||||||
|
id: string
|
||||||
|
license_id: string
|
||||||
|
server_name: string
|
||||||
|
max_players: number | null
|
||||||
|
world_size: number | null
|
||||||
|
current_seed: number | null
|
||||||
|
auto_restart_enabled: boolean
|
||||||
|
auto_restart_cron: string | null
|
||||||
|
crash_recovery_enabled: boolean
|
||||||
|
force_wipe_eligible: boolean
|
||||||
|
auto_update_on_force_wipe: boolean
|
||||||
|
config_overrides: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerStats {
|
||||||
|
player_count: number
|
||||||
|
max_players: number
|
||||||
|
fps: number
|
||||||
|
entity_count: number
|
||||||
|
uptime_seconds: number
|
||||||
|
memory_usage_mb: number
|
||||||
|
recorded_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WipeProfile {
|
||||||
|
id: string
|
||||||
|
license_id: string
|
||||||
|
profile_name: string
|
||||||
|
description: string | null
|
||||||
|
pre_wipe_config: PreWipeConfig
|
||||||
|
post_wipe_config: PostWipeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PreWipeConfig {
|
||||||
|
enabled: boolean
|
||||||
|
backup_before_wipe: boolean
|
||||||
|
countdown_warnings: number[]
|
||||||
|
countdown_unit: string
|
||||||
|
countdown_messages: Record<string, string>
|
||||||
|
kick_players_before_wipe: boolean
|
||||||
|
kick_message: string
|
||||||
|
run_final_save: boolean
|
||||||
|
discord_pre_announce: boolean
|
||||||
|
pushbullet_notify: boolean
|
||||||
|
custom_commands_before: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostWipeConfig {
|
||||||
|
enabled: boolean
|
||||||
|
verify_server_started: boolean
|
||||||
|
verify_correct_map: boolean
|
||||||
|
verify_plugins_loaded: boolean
|
||||||
|
verify_player_slots_open: boolean
|
||||||
|
max_restart_attempts: number
|
||||||
|
health_check_timeout_seconds: number
|
||||||
|
discord_post_announce: boolean
|
||||||
|
pushbullet_notify: boolean
|
||||||
|
rollback_on_failure: boolean
|
||||||
|
post_wipe_commands: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WipeSchedule {
|
||||||
|
id: string
|
||||||
|
license_id: string
|
||||||
|
wipe_profile_id: string
|
||||||
|
schedule_name: string
|
||||||
|
wipe_type: 'map' | 'blueprint' | 'full'
|
||||||
|
cron_expression: string
|
||||||
|
timezone: string
|
||||||
|
wipe_blueprints: boolean
|
||||||
|
is_active: boolean
|
||||||
|
next_scheduled_run: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WipeHistory {
|
||||||
|
id: string
|
||||||
|
wipe_type: string
|
||||||
|
trigger_type: 'scheduled' | 'manual' | 'force_wipe'
|
||||||
|
status: 'pending' | 'pre_wipe' | 'wiping' | 'post_wipe' | 'success' | 'failed' | 'rolled_back'
|
||||||
|
started_at: string | null
|
||||||
|
completed_at: string | null
|
||||||
|
map_used: string | null
|
||||||
|
plugins_wiped: string[]
|
||||||
|
plugins_preserved: string[]
|
||||||
|
error_message: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MapEntry {
|
||||||
|
id: string
|
||||||
|
filename: string
|
||||||
|
display_name: string
|
||||||
|
file_size_bytes: number
|
||||||
|
map_type: 'custom' | 'procedural'
|
||||||
|
seed: number | null
|
||||||
|
world_size: number | null
|
||||||
|
thumbnail_path: string | null
|
||||||
|
checksum: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginEntry {
|
||||||
|
id: string
|
||||||
|
plugin_name: string
|
||||||
|
plugin_version: string | null
|
||||||
|
source: 'umod' | 'corrosion_module' | 'manual'
|
||||||
|
is_installed: boolean
|
||||||
|
is_loaded: boolean
|
||||||
|
wipe_on_map: boolean
|
||||||
|
wipe_on_bp: boolean
|
||||||
|
wipe_on_full: boolean
|
||||||
|
never_wipe: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameAdmin {
|
||||||
|
id: string
|
||||||
|
steam_id: string
|
||||||
|
display_name: string
|
||||||
|
admin_level: 'owner' | 'admin' | 'moderator'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TeamMember {
|
||||||
|
id: string
|
||||||
|
user_id: string
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
role_name: string
|
||||||
|
accepted_at: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Role {
|
||||||
|
id: string
|
||||||
|
role_name: string
|
||||||
|
is_system_default: boolean
|
||||||
|
permissions: Record<string, boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatMessage {
|
||||||
|
id: string
|
||||||
|
steam_id: string
|
||||||
|
player_name: string
|
||||||
|
channel: 'global' | 'team' | 'server'
|
||||||
|
message: string
|
||||||
|
flagged: boolean
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotificationConfig {
|
||||||
|
discord_webhook_url: string | null
|
||||||
|
discord_enabled: boolean
|
||||||
|
pushbullet_api_key: string | null
|
||||||
|
pushbullet_enabled: boolean
|
||||||
|
email_alerts_enabled: boolean
|
||||||
|
notify_wipe_start: boolean
|
||||||
|
notify_wipe_complete: boolean
|
||||||
|
notify_wipe_failed: boolean
|
||||||
|
notify_server_crash: boolean
|
||||||
|
notify_server_offline: boolean
|
||||||
|
notify_store_purchase: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebstoreItem {
|
||||||
|
id: string
|
||||||
|
category_id: string
|
||||||
|
item_name: string
|
||||||
|
description: string | null
|
||||||
|
price: number
|
||||||
|
image_url: string | null
|
||||||
|
item_type: 'kit' | 'rank' | 'currency' | 'custom_command'
|
||||||
|
delivery_config: { commands: string[] }
|
||||||
|
is_active: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebstoreTransaction {
|
||||||
|
id: string
|
||||||
|
item_id: string
|
||||||
|
buyer_steam_id: string
|
||||||
|
buyer_name: string | null
|
||||||
|
amount: number
|
||||||
|
currency: string
|
||||||
|
status: 'pending' | 'paid' | 'delivered' | 'failed' | 'refunded'
|
||||||
|
delivered_at: string | null
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
10
frontend/src/views/admin/AnalyticsView.vue
Normal file
10
frontend/src/views/admin/AnalyticsView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Phase 2 — Implement analytics dashboard
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Analytics</h1>
|
||||||
|
<p class="text-neutral-400">Coming soon — player trends, performance metrics, and server analytics.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/ChatLogView.vue
Normal file
10
frontend/src/views/admin/ChatLogView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement real-time chat feed from the game server
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Chat Log</h1>
|
||||||
|
<p class="text-neutral-400">Real-time chat feed from your Rust server with search and filtering.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/ConsoleView.vue
Normal file
10
frontend/src/views/admin/ConsoleView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement live server console output with command input
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Server Console</h1>
|
||||||
|
<p class="text-neutral-400">Live console output and command interface for your Rust server.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/DashboardView.vue
Normal file
10
frontend/src/views/admin/DashboardView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement server overview dashboard with key metrics and status
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Dashboard</h1>
|
||||||
|
<p class="text-neutral-400">Server overview — players online, performance metrics, and quick actions.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/MapsView.vue
Normal file
10
frontend/src/views/admin/MapsView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement map library with upload and rotation management
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Map Library</h1>
|
||||||
|
<p class="text-neutral-400">Upload custom maps and manage map rotation for your server.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/ModuleStoreView.vue
Normal file
10
frontend/src/views/admin/ModuleStoreView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement module store browser for purchasing add-on modules
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Module Store</h1>
|
||||||
|
<p class="text-neutral-400">Browse and purchase add-on modules to extend your panel capabilities.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/NotificationsView.vue
Normal file
10
frontend/src/views/admin/NotificationsView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement notification channel configuration for Discord and Pushbullet
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Notifications</h1>
|
||||||
|
<p class="text-neutral-400">Configure Discord webhooks, Pushbullet, and other notification channels.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/PlayersView.vue
Normal file
10
frontend/src/views/admin/PlayersView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement player list with kick, ban, and moderation actions
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Player Management</h1>
|
||||||
|
<p class="text-neutral-400">View connected players and manage kick, ban, and moderation actions.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/PluginsView.vue
Normal file
10
frontend/src/views/admin/PluginsView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement installed plugin list and uMod plugin browser
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Plugin Management</h1>
|
||||||
|
<p class="text-neutral-400">Manage installed plugins and browse the uMod plugin library.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/ServerView.vue
Normal file
10
frontend/src/views/admin/ServerView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement server configuration and start/stop/restart controls
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Server Management</h1>
|
||||||
|
<p class="text-neutral-400">Configure server settings and control start, stop, and restart operations.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/SettingsView.vue
Normal file
10
frontend/src/views/admin/SettingsView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement settings for license, subdomain, and account management
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Settings</h1>
|
||||||
|
<p class="text-neutral-400">Manage your license, subdomain configuration, and account settings.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/StoreManageView.vue
Normal file
10
frontend/src/views/admin/StoreManageView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement webstore item and category management
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Webstore Management</h1>
|
||||||
|
<p class="text-neutral-400">Manage store items, categories, and pricing for your server webstore.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/TeamView.vue
Normal file
10
frontend/src/views/admin/TeamView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement team management with role-based access control
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Team Management</h1>
|
||||||
|
<p class="text-neutral-400">Invite team members and manage role-based access to the panel.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/WipeCalendarView.vue
Normal file
10
frontend/src/views/admin/WipeCalendarView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement calendar view of scheduled and past wipes
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Wipe Calendar</h1>
|
||||||
|
<p class="text-neutral-400">Calendar view of upcoming and completed server wipes.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/WipeHistoryView.vue
Normal file
10
frontend/src/views/admin/WipeHistoryView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement wipe execution log with status and details
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Wipe History</h1>
|
||||||
|
<p class="text-neutral-400">Execution logs for all past wipes with status and details.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/WipeProfilesView.vue
Normal file
10
frontend/src/views/admin/WipeProfilesView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement wipe profile creation and management
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Wipe Profiles</h1>
|
||||||
|
<p class="text-neutral-400">Create and manage reusable wipe profiles for different reset configurations.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/admin/WipesView.vue
Normal file
10
frontend/src/views/admin/WipesView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement auto-wiper with schedules and manual trigger
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Auto-Wiper</h1>
|
||||||
|
<p class="text-neutral-400">Configure wipe schedules and trigger manual wipes.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/auth/LoginView.vue
Normal file
10
frontend/src/views/auth/LoginView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement sign-in form with email/password and license validation
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Sign In</h1>
|
||||||
|
<p class="text-neutral-400">Sign in to your Corrosion server panel.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/auth/RegisterView.vue
Normal file
10
frontend/src/views/auth/RegisterView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement registration form with license key field
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Create Account</h1>
|
||||||
|
<p class="text-neutral-400">Register a new account with your license key.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/auth/SetupWizardView.vue
Normal file
10
frontend/src/views/auth/SetupWizardView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement multi-step setup wizard for initial server configuration
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Setup Your Server</h1>
|
||||||
|
<p class="text-neutral-400">Multi-step wizard to configure your Rust server for the first time.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/public/ServerInfoView.vue
Normal file
10
frontend/src/views/public/ServerInfoView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement public-facing server information page
|
||||||
|
</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>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/public/StatusPageView.vue
Normal file
10
frontend/src/views/public/StatusPageView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement public server status page with uptime and metrics
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Server Status</h1>
|
||||||
|
<p class="text-neutral-400">Live server status, uptime history, and current player count.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
10
frontend/src/views/public/StoreView.vue
Normal file
10
frontend/src/views/public/StoreView.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// TODO: Implement public-facing webstore for player purchases
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold text-neutral-100 mb-4">Store</h1>
|
||||||
|
<p class="text-neutral-400">Browse and purchase items from the server store.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
16
frontend/tsconfig.app.json
Normal file
16
frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
|
}
|
||||||
7
frontend/tsconfig.json
Normal file
7
frontend/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
26
frontend/tsconfig.node.json
Normal file
26
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
25
frontend/vite.config.ts
Normal file
25
frontend/vite.config.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5174,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user