feat: Add RaidableBases plugin config module — DB migration, NestJS CRUD, Vue editor
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
- Migration 021: raidablebases_configs table with JSONB config_data - Entity, module, controller (7 endpoints), service with NATS deploy/import - Frontend: 4-tab editor (General, Difficulty, NPC, Loot & Rewards) - Pinia store, types, router route, sidebar nav with Swords icon - Top 30 most common settings with actual RaidableBases.json key paths - Difficulty sub-tabs for Easy/Medium/Hard/Expert/Nightmare with spawn day toggles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
||||
DoorOpen,
|
||||
Gift,
|
||||
Flame,
|
||||
Swords,
|
||||
Menu,
|
||||
X,
|
||||
} from 'lucide-vue-next'
|
||||
@@ -58,6 +59,7 @@ const navItems = [
|
||||
{ name: 'Furnace Splitter', path: '/furnace-splitter', icon: Flame, permission: 'furnacesplitter.view' },
|
||||
{ name: 'Better Chat', path: '/better-chat', icon: MessageSquare, permission: 'betterchat.view' },
|
||||
{ name: 'Timed Execute', path: '/timed-execute', icon: Clock, permission: 'timedexecute.view' },
|
||||
{ name: 'Raidable Bases', path: '/raidable-bases', icon: Swords, permission: 'raidablebases.view' },
|
||||
{ name: 'Auto-Wiper', path: '/wipes', icon: RefreshCw, permission: 'wipes.view' },
|
||||
{ name: 'Maps', path: '/maps', icon: Map, permission: 'maps.view' },
|
||||
{ name: 'Chat Log', path: '/chat', icon: MessageSquare, permission: 'chat.view' },
|
||||
|
||||
@@ -150,6 +150,11 @@ const panelRoutes: RouteRecordRaw[] = [
|
||||
name: 'timed-execute',
|
||||
component: () => import('@/views/admin/TimedExecuteView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'raidable-bases',
|
||||
name: 'raidable-bases',
|
||||
component: () => import('@/views/admin/RaidableBasesView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'wipes',
|
||||
name: 'wipes',
|
||||
|
||||
145
frontend/src/stores/raidablebases.ts
Normal file
145
frontend/src/stores/raidablebases.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useApi } from '@/composables/useApi'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import type { RaidableBasesConfigSummary, RaidableBasesConfigFull, RaidableBasesApplyResult } from '@/types'
|
||||
|
||||
export const useRaidableBasesStore = defineStore('raidablebases', () => {
|
||||
const configs = ref<RaidableBasesConfigSummary[]>([])
|
||||
const currentConfig = ref<RaidableBasesConfigFull | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const isSaving = ref(false)
|
||||
const isApplying = ref(false)
|
||||
const isDirty = ref(false)
|
||||
const api = useApi()
|
||||
const toast = useToastStore()
|
||||
|
||||
async function fetchConfigs() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const res = await api.get<{ configs: RaidableBasesConfigSummary[] }>('/raidablebases/configs')
|
||||
configs.value = res.configs
|
||||
} catch (err) {
|
||||
toast.error((err as Error).message)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadConfig(id: string) {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const res = await api.get<{ config: RaidableBasesConfigFull }>(`/raidablebases/configs/${id}`)
|
||||
currentConfig.value = res.config
|
||||
isDirty.value = false
|
||||
} catch (err) {
|
||||
toast.error((err as Error).message)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createConfig(name: string, description?: string) {
|
||||
try {
|
||||
const res = await api.post<{ config: RaidableBasesConfigFull }>('/raidablebases/configs', {
|
||||
config_name: name,
|
||||
description,
|
||||
})
|
||||
await fetchConfigs()
|
||||
currentConfig.value = res.config
|
||||
isDirty.value = false
|
||||
toast.success(`Config "${name}" created`)
|
||||
return res.config
|
||||
} catch (err) {
|
||||
toast.error((err as Error).message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCurrentConfig() {
|
||||
if (!currentConfig.value) return
|
||||
isSaving.value = true
|
||||
try {
|
||||
await api.put(`/raidablebases/configs/${currentConfig.value.id}`, {
|
||||
config_name: currentConfig.value.config_name,
|
||||
description: currentConfig.value.description,
|
||||
config_data: currentConfig.value.config_data,
|
||||
})
|
||||
isDirty.value = false
|
||||
await fetchConfigs()
|
||||
toast.success('Config saved')
|
||||
} catch (err) {
|
||||
toast.error((err as Error).message)
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteConfig(id: string) {
|
||||
try {
|
||||
await api.del(`/raidablebases/configs/${id}`)
|
||||
if (currentConfig.value?.id === id) {
|
||||
currentConfig.value = null
|
||||
}
|
||||
await fetchConfigs()
|
||||
toast.success('Config deleted')
|
||||
} catch (err) {
|
||||
toast.error((err as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
async function applyToServer(id: string) {
|
||||
isApplying.value = true
|
||||
try {
|
||||
const res = await api.post<RaidableBasesApplyResult>(`/raidablebases/configs/${id}/apply`)
|
||||
await fetchConfigs()
|
||||
toast.success(res.message)
|
||||
return res
|
||||
} catch (err) {
|
||||
toast.error((err as Error).message)
|
||||
return null
|
||||
} finally {
|
||||
isApplying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function importFromServer(configName: string) {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const res = await api.post<{ config: RaidableBasesConfigFull }>('/raidablebases/import-from-server', {
|
||||
config_name: configName,
|
||||
})
|
||||
await fetchConfigs()
|
||||
currentConfig.value = res.config
|
||||
isDirty.value = false
|
||||
toast.success(`Config imported from server as "${configName}"`)
|
||||
return res.config
|
||||
} catch (err) {
|
||||
toast.error((err as Error).message)
|
||||
return null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function markDirty() {
|
||||
isDirty.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
configs,
|
||||
currentConfig,
|
||||
isLoading,
|
||||
isSaving,
|
||||
isApplying,
|
||||
isDirty,
|
||||
fetchConfigs,
|
||||
loadConfig,
|
||||
createConfig,
|
||||
saveCurrentConfig,
|
||||
deleteConfig,
|
||||
applyToServer,
|
||||
importFromServer,
|
||||
markDirty,
|
||||
}
|
||||
})
|
||||
@@ -708,3 +708,30 @@ export interface TimedExecuteApplyResult {
|
||||
message: string
|
||||
config_name: string
|
||||
}
|
||||
|
||||
// RaidableBases Config types
|
||||
export interface RaidableBasesConfigSummary {
|
||||
id: string
|
||||
config_name: string
|
||||
description: string | null
|
||||
is_active: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface RaidableBasesConfigFull {
|
||||
id: string
|
||||
license_id: string
|
||||
config_name: string
|
||||
description: string | null
|
||||
config_data: Record<string, any>
|
||||
is_active: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface RaidableBasesApplyResult {
|
||||
success: boolean
|
||||
message: string
|
||||
config_name: string
|
||||
}
|
||||
|
||||
1143
frontend/src/views/admin/RaidableBasesView.vue
Normal file
1143
frontend/src/views/admin/RaidableBasesView.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user