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