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:
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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user