Compare commits
3 Commits
9a5b93dd08
...
v1.0.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
180631989a | ||
|
|
23decd9b08 | ||
|
|
8b84bba165 |
@@ -1,4 +1,4 @@
|
||||
name: Build Companion Agent
|
||||
name: Build Host Agent
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -26,19 +26,19 @@ jobs:
|
||||
run: |
|
||||
cd companion-agent
|
||||
mkdir -p bin
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.version=${{ steps.version.outputs.VERSION }}" -o bin/corrosion-companion-linux-amd64 ./cmd/agent
|
||||
chmod +x bin/corrosion-companion-linux-amd64
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.version=${{ steps.version.outputs.VERSION }}" -o bin/corrosion-host-agent-linux-amd64 ./cmd/agent
|
||||
chmod +x bin/corrosion-host-agent-linux-amd64
|
||||
|
||||
- name: Build Windows AMD64
|
||||
run: |
|
||||
cd companion-agent
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.version=${{ steps.version.outputs.VERSION }}" -o bin/corrosion-companion-windows-amd64.exe ./cmd/agent
|
||||
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.version=${{ steps.version.outputs.VERSION }}" -o bin/corrosion-host-agent-windows-amd64.exe ./cmd/agent
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd companion-agent/bin
|
||||
sha256sum corrosion-companion-linux-amd64 > checksums.txt
|
||||
sha256sum corrosion-companion-windows-amd64.exe >> checksums.txt
|
||||
sha256sum corrosion-host-agent-linux-amd64 > checksums.txt
|
||||
sha256sum corrosion-host-agent-windows-amd64.exe >> checksums.txt
|
||||
cat checksums.txt
|
||||
|
||||
- name: Create Release
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
-H "Authorization: token ${RELEASE_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\": \"${VERSION}\", \"name\": \"Companion Agent ${VERSION}\", \"body\": \"Companion Agent release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
|
||||
-d "{\"tag_name\": \"${VERSION}\", \"name\": \"Corrosion Host Agent ${VERSION}\", \"body\": \"Corrosion Host Agent release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
|
||||
"${API_URL}/repos/${REPO}/releases")
|
||||
RELEASE_ID=$(echo "$RESPONSE" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
|
||||
|
||||
@@ -68,15 +68,15 @@ jobs:
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token ${RELEASE_TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @companion-agent/bin/corrosion-companion-linux-amd64 \
|
||||
"${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=corrosion-companion-linux-amd64"
|
||||
--data-binary @companion-agent/bin/corrosion-host-agent-linux-amd64 \
|
||||
"${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=corrosion-host-agent-linux-amd64"
|
||||
|
||||
# Upload Windows binary
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token ${RELEASE_TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @companion-agent/bin/corrosion-companion-windows-amd64.exe \
|
||||
"${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=corrosion-companion-windows-amd64.exe"
|
||||
--data-binary @companion-agent/bin/corrosion-host-agent-windows-amd64.exe \
|
||||
"${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=corrosion-host-agent-windows-amd64.exe"
|
||||
|
||||
# Upload checksums
|
||||
curl -s -X POST \
|
||||
@@ -89,43 +89,43 @@ jobs:
|
||||
run: |
|
||||
CDN_URL="https://cdn.corrosionmgmt.com"
|
||||
|
||||
# Upload Linux binary to /companion/latest/
|
||||
# Upload Linux binary to /host-agent/latest/
|
||||
curl -s -X POST \
|
||||
-F "file=@companion-agent/bin/corrosion-companion-linux-amd64" \
|
||||
"${CDN_URL}/companion/latest/corrosion-companion-linux-amd64"
|
||||
-F "file=@companion-agent/bin/corrosion-host-agent-linux-amd64" \
|
||||
"${CDN_URL}/host-agent/latest/corrosion-host-agent-linux-amd64"
|
||||
|
||||
# Upload Windows binary to /companion/latest/
|
||||
# Upload Windows binary to /host-agent/latest/
|
||||
curl -s -X POST \
|
||||
-F "file=@companion-agent/bin/corrosion-companion-windows-amd64.exe" \
|
||||
"${CDN_URL}/companion/latest/corrosion-companion-windows-amd64.exe"
|
||||
-F "file=@companion-agent/bin/corrosion-host-agent-windows-amd64.exe" \
|
||||
"${CDN_URL}/host-agent/latest/corrosion-host-agent-windows-amd64.exe"
|
||||
|
||||
# Upload checksums
|
||||
curl -s -X POST \
|
||||
-F "file=@companion-agent/bin/checksums.txt" \
|
||||
"${CDN_URL}/companion/latest/checksums.txt"
|
||||
"${CDN_URL}/host-agent/latest/checksums.txt"
|
||||
|
||||
# Also upload versioned copies
|
||||
VERSION=${{ steps.version.outputs.VERSION }}
|
||||
curl -s -X POST \
|
||||
-F "file=@companion-agent/bin/corrosion-companion-linux-amd64" \
|
||||
"${CDN_URL}/companion/${VERSION}/corrosion-companion-linux-amd64"
|
||||
-F "file=@companion-agent/bin/corrosion-host-agent-linux-amd64" \
|
||||
"${CDN_URL}/host-agent/${VERSION}/corrosion-host-agent-linux-amd64"
|
||||
curl -s -X POST \
|
||||
-F "file=@companion-agent/bin/corrosion-companion-windows-amd64.exe" \
|
||||
"${CDN_URL}/companion/${VERSION}/corrosion-companion-windows-amd64.exe"
|
||||
-F "file=@companion-agent/bin/corrosion-host-agent-windows-amd64.exe" \
|
||||
"${CDN_URL}/host-agent/${VERSION}/corrosion-host-agent-windows-amd64.exe"
|
||||
curl -s -X POST \
|
||||
-F "file=@companion-agent/bin/checksums.txt" \
|
||||
"${CDN_URL}/companion/${VERSION}/checksums.txt"
|
||||
"${CDN_URL}/host-agent/${VERSION}/checksums.txt"
|
||||
|
||||
echo "CDN upload complete: ${CDN_URL}/companion/latest/"
|
||||
echo "CDN upload complete: ${CDN_URL}/host-agent/latest/"
|
||||
|
||||
- name: Build Summary
|
||||
run: |
|
||||
echo "## Companion Agent Build Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Corrosion Host Agent Build Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ steps.version.outputs.VERSION }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** ${GITHUB_SHA:0:7}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Built Artifacts:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Linux AMD64 ($(stat -c%s companion-agent/bin/corrosion-companion-linux-amd64) bytes)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Windows AMD64 ($(stat -c%s companion-agent/bin/corrosion-companion-windows-amd64.exe) bytes)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Linux AMD64 ($(stat -c%s companion-agent/bin/corrosion-host-agent-linux-amd64) bytes)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Windows AMD64 ($(stat -c%s companion-agent/bin/corrosion-host-agent-windows-amd64.exe) bytes)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- SHA256 checksums" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.PHONY: all build build-linux build-windows clean test run
|
||||
|
||||
# Binary names
|
||||
BINARY_NAME=corrosion-companion
|
||||
BINARY_NAME=corrosion-host-agent
|
||||
BINARY_LINUX=$(BINARY_NAME)-linux-amd64
|
||||
BINARY_WINDOWS=$(BINARY_NAME)-windows-amd64.exe
|
||||
|
||||
@@ -66,10 +66,10 @@ run: build-local
|
||||
install-service:
|
||||
@echo "Installing systemd service..."
|
||||
@sudo cp $(BUILD_DIR)/$(BINARY_LINUX) /usr/local/bin/$(BINARY_NAME)
|
||||
@sudo cp deployment/corrosion-companion.service /etc/systemd/system/
|
||||
@sudo cp deployment/corrosion-host-agent.service /etc/systemd/system/
|
||||
@sudo systemctl daemon-reload
|
||||
@sudo systemctl enable corrosion-companion
|
||||
@echo "Service installed. Configure /etc/corrosion-companion/.env then start with: sudo systemctl start corrosion-companion"
|
||||
@sudo systemctl enable corrosion-host-agent
|
||||
@echo "Service installed. Configure /etc/corrosion-host-agent/.env then start with: sudo systemctl start corrosion-host-agent"
|
||||
|
||||
# Development helpers
|
||||
dev: build-local
|
||||
|
||||
@@ -8,6 +8,13 @@ services:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-corrosion_dev}
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
# Auto-build the schema on a FRESH database. Postgres runs these ONLY when
|
||||
# the data dir is empty (first boot or after a volume reset), so it never
|
||||
# touches an existing volume — it just makes a fresh DB self-heal: the full
|
||||
# schema is applied in order from the sqlx migrations (001..NNN), then the
|
||||
# API's bootstrap seeds the admin. Rebuilds (with the volume kept) are a
|
||||
# no-op here; the data persists. Only `down -v` / volume prune loses data.
|
||||
- ../backend/migrations:/docker-entrypoint-initdb.d:ro
|
||||
ports:
|
||||
- "8101:5432"
|
||||
healthcheck:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
1
frontend/src/app-version.d.ts
vendored
Normal file
1
frontend/src/app-version.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare const __APP_VERSION__: string
|
||||
@@ -1,15 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* DashboardLayout — game-aware app shell (Phase C redesign).
|
||||
* Replaces the old Tailwind-only sidebar with the DS component set.
|
||||
* Preserves: navSections, permission gating, super-admin section, logout, RouterView.
|
||||
* Adds: GameSwitcher, Logo, DS NavItem, agent-health footer, topbar w/ search + theme toggle.
|
||||
* Nav is driven by GAME_PROFILES[activeGame].nav — switching the GameSwitcher
|
||||
* visibly changes nav items, labels, and sections per game.
|
||||
* Preserves: permission gating, super-admin section, logout, mobile sidebar,
|
||||
* GameSwitcher, agent-health footer, topbar.
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { RouterView, useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useServerStore } from '@/stores/server'
|
||||
import { useThemeGame } from '@/composables/useThemeGame'
|
||||
import { useGameProfile } from '@/config/gameProfiles'
|
||||
import type { NavSection, NavItemDef } from '@/config/gameProfiles'
|
||||
import { safeDate } from '@/utils/formatters'
|
||||
import Logo from '@/components/ds/brand/Logo.vue'
|
||||
import Badge from '@/components/ds/core/Badge.vue'
|
||||
import StatusDot from '@/components/ds/core/StatusDot.vue'
|
||||
@@ -33,7 +37,7 @@ const sidebarOpen = ref(false)
|
||||
function closeSidebar() { sidebarOpen.value = false }
|
||||
|
||||
// ---- App version ----
|
||||
const APP_VERSION = '1.0.8'
|
||||
const APP_VERSION = __APP_VERSION__
|
||||
|
||||
// ---- Game switcher ----
|
||||
const GAME_OPTIONS: GameOption[] = [
|
||||
@@ -53,61 +57,15 @@ function onActiveGame(val: string) {
|
||||
setActiveGame(val as ActiveGame)
|
||||
}
|
||||
|
||||
// ---- Navigation ----
|
||||
type NavItemDef = { name: string; path: string; icon: string; permission: string | null }
|
||||
type NavSection = { label: string; items: NavItemDef[] }
|
||||
|
||||
const navSections: NavSection[] = [
|
||||
{
|
||||
label: '',
|
||||
items: [
|
||||
{ name: 'Dashboard', path: '/', icon: 'layout-dashboard', permission: null },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Server',
|
||||
items: [
|
||||
{ name: 'Server', path: '/server', icon: 'server', permission: 'server.view' },
|
||||
{ name: 'Console', path: '/console', icon: 'terminal', permission: 'console.view' },
|
||||
{ name: 'Players', path: '/players', icon: 'users', permission: 'players.view' },
|
||||
{ name: 'Plugins', path: '/plugins', icon: 'puzzle', permission: 'plugins.view' },
|
||||
{ name: 'File manager', path: '/files', icon: 'folder-open', permission: 'files.view' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Plugin configs',
|
||||
items: [
|
||||
{ name: 'Plugin configs', path: '/plugin-configs', icon: 'puzzle', permission: null },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Operations',
|
||||
items: [
|
||||
{ name: 'Wipe manager', path: '/wipes', icon: 'trash-2', permission: 'wipes.view' },
|
||||
{ name: 'Maps', path: '/maps', icon: 'map', permission: 'maps.view' },
|
||||
{ name: 'Schedules', path: '/schedules', icon: 'calendar-clock', permission: 'schedules.view' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Monitoring',
|
||||
items: [
|
||||
{ name: 'Chat log', path: '/chat', icon: 'message-square', permission: 'chat.view' },
|
||||
{ name: 'Analytics', path: '/analytics', icon: 'bar-chart-3', permission: 'analytics.view' },
|
||||
{ name: 'Alerts', path: '/alerts', icon: 'triangle-alert', permission: 'alerts.view' },
|
||||
{ name: 'Notifications', path: '/notifications', icon: 'bell', permission: 'notifications.view' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Management',
|
||||
items: [
|
||||
{ name: 'Team', path: '/team', icon: 'users', permission: null },
|
||||
{ name: 'Store', path: '/store/config', icon: 'shopping-cart', permission: 'store.view' },
|
||||
{ name: 'Modules', path: '/modules', icon: 'layers', permission: 'modules.view' },
|
||||
{ name: 'Changelog', path: '/changelog', icon: 'file-text', permission: 'changelog.view' },
|
||||
{ name: 'Settings', path: '/settings', icon: 'settings', permission: 'settings.view' },
|
||||
],
|
||||
},
|
||||
]
|
||||
// ---- Navigation — driven by the game profile registry ----
|
||||
/**
|
||||
* For 'all', fall back to rust (superset nav). For a specific game, look up
|
||||
* its profile. noUncheckedIndexedAccess-safe: always ?? GAME_PROFILES.rust.
|
||||
*/
|
||||
const activeNavSections = computed<NavSection[]>(() => {
|
||||
const game = activeGame.value === 'all' ? 'rust' : activeGame.value
|
||||
return (useGameProfile(game)).nav
|
||||
})
|
||||
|
||||
const adminNavItems = [
|
||||
{ name: 'Admin home', path: '/admin', icon: 'shield' },
|
||||
@@ -137,6 +95,8 @@ function hasVisibleItems(section: NavSection): boolean {
|
||||
}
|
||||
|
||||
// ---- Agent health ----
|
||||
const hasAgent = computed(() => server.connection !== null)
|
||||
|
||||
const agentTone = computed(() => {
|
||||
const cs = server.connection?.connection_status
|
||||
if (cs === 'connected') return 'online' as const
|
||||
@@ -149,18 +109,23 @@ const agentLabel = computed(() => {
|
||||
if (cs === 'degraded') return 'Degraded'
|
||||
return 'Offline'
|
||||
})
|
||||
const agentName = computed(() => {
|
||||
const ip = server.connection?.server_ip
|
||||
return ip ?? 'asgard-01'
|
||||
const agentName = computed(() => server.connection?.server_ip ?? 'Host agent')
|
||||
|
||||
const agentMetaLine = computed(() => {
|
||||
const cs = server.connection?.connection_status
|
||||
let line = cs === 'connected' ? 'Connected' : server.connection?.companion_last_seen
|
||||
? `Last seen ${safeDate(server.connection.companion_last_seen)}`
|
||||
: 'Awaiting first heartbeat'
|
||||
if (server.stats) {
|
||||
line += ` · ${server.stats.player_count}/${server.stats.max_players} players`
|
||||
}
|
||||
return line
|
||||
})
|
||||
|
||||
// ---- Topbar ----
|
||||
const serverName = computed(() => auth.license?.server_name ?? 'Your servers')
|
||||
const userName = computed(() => auth.user?.username ?? '')
|
||||
const themeIcon = computed(() => theme.value === 'dark' ? 'sun' : 'moon')
|
||||
|
||||
// ---- Import computed from vue (missed above) ----
|
||||
import { computed } from 'vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -197,20 +162,20 @@ import { computed } from 'vue'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<!-- Navigation — sections driven by GAME_PROFILES[activeGame].nav -->
|
||||
<nav class="side__nav">
|
||||
<template v-for="section in navSections" :key="section.label">
|
||||
<template v-for="section in activeNavSections" :key="section.label">
|
||||
<template v-if="hasVisibleItems(section)">
|
||||
<div class="side__sec">
|
||||
<div v-if="section.label" class="t-eyebrow side__lbl">{{ section.label }}</div>
|
||||
<NavItem
|
||||
v-for="item in section.items"
|
||||
v-show="canShowNavItem(item)"
|
||||
:key="item.path"
|
||||
:key="item.route"
|
||||
:icon="item.icon"
|
||||
:label="item.name"
|
||||
:active="isActive(item.path)"
|
||||
@click="navigate(item.path)"
|
||||
:label="item.label"
|
||||
:active="isActive(item.route)"
|
||||
@click="navigate(item.route)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -230,18 +195,24 @@ import { computed } from 'vue'
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Agent health footer -->
|
||||
<!-- Host agent footer -->
|
||||
<div class="side__foot">
|
||||
<div class="agent">
|
||||
<!-- Connected: real IP + status badge + meta line -->
|
||||
<div v-if="hasAgent" class="agent">
|
||||
<div class="agent__row">
|
||||
<StatusDot :tone="agentTone" :pulse="agentTone === 'online'" />
|
||||
<span class="agent__name">{{ agentName }}</span>
|
||||
<Badge :tone="agentTone" size="md">{{ agentLabel }}</Badge>
|
||||
</div>
|
||||
<div class="agent__meta">
|
||||
Agent v{{ APP_VERSION }}
|
||||
<template v-if="server.stats"> · {{ server.stats.player_count }}/{{ server.stats.max_players }} players</template>
|
||||
<div class="agent__meta">{{ agentMetaLine }}</div>
|
||||
</div>
|
||||
<!-- Not connected: honest empty state -->
|
||||
<div v-else class="agent agent--empty">
|
||||
<div class="agent__row">
|
||||
<StatusDot tone="offline" />
|
||||
<span class="agent__name agent__name--muted">No host agent connected</span>
|
||||
</div>
|
||||
<div class="agent__meta">Install the Corrosion host agent from the Server page</div>
|
||||
</div>
|
||||
<!-- User / logout row -->
|
||||
<div class="side__user">
|
||||
@@ -419,6 +390,13 @@ body { margin: 0; overflow: hidden; }
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.agent--empty { opacity: 0.7; }
|
||||
|
||||
.agent__name--muted {
|
||||
color: var(--text-tertiary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.side__user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* gameProfiles.ts — Source of truth for per-game UI adaptation.
|
||||
*
|
||||
* Every game-specific label, terminology, Steam app ID, management model,
|
||||
* and stat field list lives here. The dashboard, server cards, wipe manager,
|
||||
* and any future multi-game surface should key off this registry — never
|
||||
* hard-code game-specific strings in components.
|
||||
* stat field list, AND sidebar nav lives here. The dashboard, server cards,
|
||||
* wipe manager, sidebar, and any future multi-game surface should key off this
|
||||
* registry — never hard-code game-specific strings in components.
|
||||
*
|
||||
* Backend status: the backend has NO game field on licenses yet. Today every
|
||||
* license is implicitly Rust. This registry is ready: when the backend adds a
|
||||
@@ -15,6 +15,26 @@
|
||||
* GAME_PROFILES. Nothing else changes.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Nav structure — drives the per-game sidebar
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** A single sidebar nav item. route must be an existing panel route path. */
|
||||
export interface NavItemDef {
|
||||
label: string
|
||||
route: string
|
||||
icon: string
|
||||
/** Permission key required to show this item (e.g. 'plugins.view'). Null = always visible. */
|
||||
permission: string | null
|
||||
}
|
||||
|
||||
/** A labelled section grouping nav items in the sidebar. */
|
||||
export interface NavSection {
|
||||
/** Section heading (eyebrow text). Empty string = no heading. */
|
||||
label: string
|
||||
items: NavItemDef[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Union types — exhaustive, never widen to string
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -87,12 +107,67 @@ export interface GameProfile {
|
||||
* First entry is always Players; subsequent entries are game-specific.
|
||||
*/
|
||||
statFields: [string, string, string]
|
||||
/**
|
||||
* Per-game sidebar navigation. Ordered list of sections, each with items.
|
||||
* Items MUST use only existing panel routes (see router/index.ts).
|
||||
* The sidebar renders exactly these sections for the active game.
|
||||
*/
|
||||
nav: NavSection[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared nav building blocks — reused across game nav definitions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const NAV_DASHBOARD: NavItemDef = { label: 'Dashboard', route: '/', icon: 'layout-dashboard', permission: null }
|
||||
const NAV_SERVER: NavItemDef = { label: 'Server', route: '/server', icon: 'server', permission: 'server.view' }
|
||||
const NAV_CONSOLE: NavItemDef = { label: 'Console', route: '/console', icon: 'terminal', permission: 'console.view' }
|
||||
const NAV_PLAYERS: NavItemDef = { label: 'Players', route: '/players', icon: 'users', permission: 'players.view' }
|
||||
const NAV_PLUGINS: NavItemDef = { label: 'Plugins (uMod)', route: '/plugins', icon: 'puzzle', permission: 'plugins.view' }
|
||||
const NAV_FILES: NavItemDef = { label: 'File manager', route: '/files', icon: 'folder-open', permission: 'files.view' }
|
||||
const NAV_PLUGIN_CONFIGS: NavItemDef = { label: 'Plugin configs', route: '/plugin-configs', icon: 'sliders', permission: null }
|
||||
const NAV_SCHEDULES: NavItemDef = { label: 'Schedules', route: '/schedules', icon: 'calendar-clock', permission: 'schedules.view' }
|
||||
const NAV_CHAT: NavItemDef = { label: 'Chat log', route: '/chat', icon: 'message-square', permission: 'chat.view' }
|
||||
const NAV_ANALYTICS: NavItemDef = { label: 'Analytics', route: '/analytics', icon: 'bar-chart-3', permission: 'analytics.view' }
|
||||
const NAV_ALERTS: NavItemDef = { label: 'Alerts', route: '/alerts', icon: 'triangle-alert', permission: 'alerts.view' }
|
||||
const NAV_NOTIFICATIONS: NavItemDef = { label: 'Notifications', route: '/notifications', icon: 'bell', permission: 'notifications.view' }
|
||||
const NAV_TEAM: NavItemDef = { label: 'Team', route: '/team', icon: 'users', permission: null }
|
||||
const NAV_STORE: NavItemDef = { label: 'Store', route: '/store/config', icon: 'shopping-cart', permission: 'store.view' }
|
||||
const NAV_MODULES: NavItemDef = { label: 'Modules', route: '/modules', icon: 'layers', permission: 'modules.view' }
|
||||
const NAV_CHANGELOG: NavItemDef = { label: 'Changelog', route: '/changelog', icon: 'file-text', permission: 'changelog.view' }
|
||||
const NAV_SETTINGS: NavItemDef = { label: 'Settings', route: '/settings', icon: 'settings', permission: 'settings.view' }
|
||||
const NAV_MAPS: NavItemDef = { label: 'Maps', route: '/maps', icon: 'map', permission: 'maps.view' }
|
||||
|
||||
/** Full Rust / 'all' nav — superset used as fallback. */
|
||||
const RUST_NAV: NavSection[] = [
|
||||
{ label: '', items: [NAV_DASHBOARD] },
|
||||
{
|
||||
label: 'Server',
|
||||
items: [NAV_SERVER, NAV_CONSOLE, NAV_PLAYERS, NAV_PLUGINS, NAV_FILES],
|
||||
},
|
||||
{ label: 'Plugin configs', items: [NAV_PLUGIN_CONFIGS] },
|
||||
{
|
||||
label: 'Operations',
|
||||
items: [
|
||||
{ label: 'Wipe', route: '/wipes', icon: 'trash-2', permission: 'wipes.view' },
|
||||
NAV_MAPS,
|
||||
NAV_SCHEDULES,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Monitoring',
|
||||
items: [NAV_CHAT, NAV_ANALYTICS, NAV_ALERTS, NAV_NOTIFICATIONS],
|
||||
},
|
||||
{
|
||||
label: 'Management',
|
||||
items: [NAV_TEAM, NAV_STORE, NAV_MODULES, NAV_CHANGELOG, NAV_SETTINGS],
|
||||
},
|
||||
]
|
||||
|
||||
export const GAME_PROFILES: Record<GameId, GameProfile> = {
|
||||
rust: {
|
||||
label: 'Rust',
|
||||
@@ -109,6 +184,7 @@ export const GAME_PROFILES: Record<GameId, GameProfile> = {
|
||||
group: 'Team',
|
||||
},
|
||||
statFields: ['Players', 'uMod', 'Wipe'],
|
||||
nav: RUST_NAV,
|
||||
},
|
||||
|
||||
conan: {
|
||||
@@ -130,6 +206,30 @@ export const GAME_PROFILES: Record<GameId, GameProfile> = {
|
||||
},
|
||||
special: ['Clans', 'Thralls', 'Avatars', 'Purge', 'PvP windows'],
|
||||
statFields: ['Players', 'Clans', 'Purge'],
|
||||
nav: [
|
||||
{ label: '', items: [NAV_DASHBOARD] },
|
||||
{
|
||||
label: 'Server',
|
||||
// Conan: no uMod/Oxide; has RCON console, maps, players, files
|
||||
items: [NAV_SERVER, NAV_CONSOLE, NAV_PLAYERS, NAV_FILES],
|
||||
},
|
||||
{
|
||||
label: 'Operations',
|
||||
items: [
|
||||
{ label: 'Wipe World', route: '/wipes', icon: 'trash-2', permission: 'wipes.view' },
|
||||
NAV_MAPS,
|
||||
NAV_SCHEDULES,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Monitoring',
|
||||
items: [NAV_CHAT, NAV_ANALYTICS, NAV_ALERTS, NAV_NOTIFICATIONS],
|
||||
},
|
||||
{
|
||||
label: 'Management',
|
||||
items: [NAV_TEAM, NAV_STORE, NAV_MODULES, NAV_CHANGELOG, NAV_SETTINGS],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
soulmask: {
|
||||
@@ -151,6 +251,29 @@ export const GAME_PROFILES: Record<GameId, GameProfile> = {
|
||||
},
|
||||
special: ['Cluster', 'Tribes'],
|
||||
statFields: ['Players', 'Tribe', 'Mask'],
|
||||
nav: [
|
||||
{ label: '', items: [NAV_DASHBOARD] },
|
||||
{
|
||||
label: 'Server',
|
||||
// Soulmask: no uMod/Oxide; has RCON+GM console, players, files
|
||||
items: [NAV_SERVER, NAV_CONSOLE, NAV_PLAYERS, NAV_FILES],
|
||||
},
|
||||
{
|
||||
label: 'Operations',
|
||||
items: [
|
||||
{ label: 'World Reset', route: '/wipes', icon: 'trash-2', permission: 'wipes.view' },
|
||||
NAV_SCHEDULES,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Monitoring',
|
||||
items: [NAV_CHAT, NAV_ANALYTICS, NAV_ALERTS],
|
||||
},
|
||||
{
|
||||
label: 'Management',
|
||||
items: [NAV_TEAM, NAV_STORE, NAV_MODULES, NAV_CHANGELOG, NAV_SETTINGS],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
dune: {
|
||||
@@ -170,6 +293,34 @@ export const GAME_PROFILES: Record<GameId, GameProfile> = {
|
||||
},
|
||||
special: ['Sietches', 'Deep Desert', 'Bases', 'Landsraad'],
|
||||
statFields: ['Players', 'Sietches', 'Control'],
|
||||
nav: [
|
||||
{ label: '', items: [NAV_DASHBOARD] },
|
||||
{
|
||||
label: 'Server',
|
||||
// Dune: no RCON (uses RabbitMQ); label console "Broadcast"; no maps route; no plugins
|
||||
items: [
|
||||
NAV_SERVER,
|
||||
{ label: 'Broadcast', route: '/console', icon: 'radio', permission: 'console.view' },
|
||||
NAV_PLAYERS,
|
||||
NAV_FILES,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Operations',
|
||||
items: [
|
||||
{ label: 'Deep Desert', route: '/wipes', icon: 'wind', permission: 'wipes.view' },
|
||||
NAV_SCHEDULES,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Monitoring',
|
||||
items: [NAV_ANALYTICS, NAV_ALERTS],
|
||||
},
|
||||
{
|
||||
label: 'Management',
|
||||
items: [NAV_TEAM, NAV_STORE, NAV_CHANGELOG, NAV_SETTINGS],
|
||||
},
|
||||
],
|
||||
},
|
||||
} as const
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useWipeStore } from '@/stores/wipe'
|
||||
import { useApi } from '@/composables/useApi'
|
||||
import { useWebSocket, type WebSocketMessage } from '@/composables/useWebSocket'
|
||||
import { useGameProfile } from '@/config/gameProfiles'
|
||||
import { useThemeGame } from '@/composables/useThemeGame'
|
||||
import Panel from '@/components/ds/data/Panel.vue'
|
||||
import StatCard from '@/components/ds/data/StatCard.vue'
|
||||
import ConsoleLineDS from '@/components/ds/data/ConsoleLine.vue'
|
||||
@@ -44,10 +45,14 @@ const server = useServerStore()
|
||||
const wipeStore = useWipeStore()
|
||||
const router = useRouter()
|
||||
const api = useApi()
|
||||
const { activeGame } = useThemeGame()
|
||||
|
||||
// Today every license is Rust. When the backend adds a `game` field to the
|
||||
// license or server_config, pass it here: useGameProfile(server.config?.game ?? 'rust')
|
||||
const profile = computed(() => useGameProfile('rust'))
|
||||
// Profile follows the GameSwitcher selection. 'all' falls back to rust (neutral house skin).
|
||||
// When the backend adds a `game` field on licenses, swap activeGame for server.config?.game.
|
||||
const profile = computed(() => {
|
||||
const game = activeGame.value === 'all' ? 'rust' : activeGame.value
|
||||
return useGameProfile(game)
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Derived server state — all real, no fallbacks to fabricated values
|
||||
@@ -254,7 +259,7 @@ function navServer() { router.push('/server') }
|
||||
<EmptyState
|
||||
icon="server"
|
||||
title="No server connected"
|
||||
description="Install the companion agent on your host machine to begin managing your server from Corrosion."
|
||||
description="Install the Corrosion host agent on your host machine to begin managing your server from Corrosion."
|
||||
>
|
||||
<template #action>
|
||||
<Button icon="server" @click="navServer">Set up server</Button>
|
||||
@@ -298,7 +303,7 @@ function navServer() { router.push('/server') }
|
||||
<div class="dash__kpis">
|
||||
<StatCard
|
||||
icon="users"
|
||||
:label="profile.statFields[0] + ' online'"
|
||||
:label="(profile.statFields[0] ?? 'Players') + ' online'"
|
||||
:value="soloPlayers !== null ? String(soloPlayers) : '—'"
|
||||
:unit="soloMaxPlayers !== null ? '/' + soloMaxPlayers : ''"
|
||||
note="live via agent"
|
||||
@@ -399,7 +404,7 @@ function navServer() { router.push('/server') }
|
||||
<div class="dash__col dash__col--side">
|
||||
|
||||
<!-- Resources — real stats from agent; null = '—' -->
|
||||
<Panel title="Resources" subtitle="Companion agent telemetry">
|
||||
<Panel title="Resources" subtitle="Host agent telemetry">
|
||||
<div class="solo-meters">
|
||||
<ResourceMeter
|
||||
label="CPU"
|
||||
@@ -413,15 +418,15 @@ function navServer() { router.push('/server') }
|
||||
/>
|
||||
</div>
|
||||
<div v-if="soloCpu === null && soloRamMb === null" class="meters-note">
|
||||
Resource metrics arrive via the companion agent heartbeat.
|
||||
Resource metrics arrive via the host agent heartbeat.
|
||||
<Button size="sm" variant="ghost" icon="server" class="meters-cta" @click="navServer">
|
||||
Agent setup
|
||||
</Button>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<!-- Next wipe — real schedule from wipeStore -->
|
||||
<Panel title="Next wipe">
|
||||
<!-- Next wipe/reset — title follows game terminology -->
|
||||
<Panel :title="'Next ' + profile.terminology.reset.toLowerCase()">
|
||||
<div v-if="nextWipe" class="solo-wipe">
|
||||
<div>
|
||||
<div class="solo-wipe__type">{{ nextWipeType }}</div>
|
||||
@@ -433,8 +438,8 @@ function navServer() { router.push('/server') }
|
||||
<EmptyState
|
||||
v-else
|
||||
icon="calendar"
|
||||
title="No wipe scheduled"
|
||||
description="Configure automatic wipes in the wipe manager."
|
||||
:title="'No ' + profile.terminology.reset.toLowerCase() + ' scheduled'"
|
||||
:description="'Configure automatic ' + profile.terminology.reset.toLowerCase() + 's in the wipe manager.'"
|
||||
>
|
||||
<template #action>
|
||||
<Button size="sm" variant="outline" icon="calendar-clock" @click="navWipes">
|
||||
|
||||
@@ -485,7 +485,7 @@ onMounted(() => {
|
||||
</Panel>
|
||||
|
||||
<Alert tone="info">
|
||||
The plugin will be registered in your plugin list immediately. Your companion agent must be connected
|
||||
The plugin will be registered in your plugin list immediately. Your host agent must be connected
|
||||
for the file to be delivered to the game server. If the agent is offline, re-upload once it reconnects.
|
||||
</Alert>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { useServerStore } from '@/stores/server'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { useThemeGame } from '@/composables/useThemeGame'
|
||||
import { useGameProfile } from '@/config/gameProfiles'
|
||||
import type { DeploymentConfig, DeploymentStatus } from '@/types'
|
||||
import { useWebSocket } from '@/composables/useWebSocket'
|
||||
import Panel from '@/components/ds/data/Panel.vue'
|
||||
@@ -11,6 +13,7 @@ import Badge from '@/components/ds/core/Badge.vue'
|
||||
import StatusDot from '@/components/ds/core/StatusDot.vue'
|
||||
import Icon from '@/components/ds/core/Icon.vue'
|
||||
import Alert from '@/components/ds/feedback/Alert.vue'
|
||||
import EmptyState from '@/components/ds/feedback/EmptyState.vue'
|
||||
import Input from '@/components/ds/forms/Input.vue'
|
||||
import Switch from '@/components/ds/forms/Switch.vue'
|
||||
import Tabs from '@/components/ds/navigation/Tabs.vue'
|
||||
@@ -18,6 +21,39 @@ import Tabs from '@/components/ds/navigation/Tabs.vue'
|
||||
const server = useServerStore()
|
||||
const auth = useAuthStore()
|
||||
const toast = useToastStore()
|
||||
const { activeGame } = useThemeGame()
|
||||
|
||||
// Profile follows the GameSwitcher. 'all' defaults to rust (neutral house skin).
|
||||
const profile = computed(() => {
|
||||
const game = activeGame.value === 'all' ? 'rust' : activeGame.value
|
||||
return useGameProfile(game)
|
||||
})
|
||||
|
||||
// Game-specific derived flags
|
||||
const isRust = computed(() => profile.value.mods === 'umod')
|
||||
const hasPluginSystem = computed(() => profile.value.mods === 'umod')
|
||||
const isDockerManaged = computed(() => profile.value.managementModel === 'docker-compose')
|
||||
|
||||
// Management model human label for the identity badge
|
||||
const managementModelLabel = computed(() => {
|
||||
const m = profile.value.managementModel
|
||||
const c = profile.value.console
|
||||
if (m === 'docker-compose') {
|
||||
return profile.value.clustering === 'battlegroup' ? 'Docker · BattleGroup' : 'Docker · Compose'
|
||||
}
|
||||
if (c === 'rcon+ingame') return 'Process · RCON + In-game'
|
||||
if (c === 'rcon+gm') return 'Process · RCON + GM'
|
||||
return 'Process · RCON'
|
||||
})
|
||||
|
||||
// Clustering section label per game
|
||||
const clusterLabel = computed(() => {
|
||||
const cl = profile.value.clustering
|
||||
if (cl === 'battlegroup') return 'BattleGroups & Sietches'
|
||||
if (cl === 'main-client') return 'Cluster'
|
||||
if (cl === 'character-transfer') return 'Clans & Character Transfer'
|
||||
return ''
|
||||
})
|
||||
|
||||
const editMode = ref(false)
|
||||
const saving = ref(false)
|
||||
@@ -64,22 +100,22 @@ const agentLastSeenLabel = computed(() => {
|
||||
const licenseKey = computed(() => auth.license?.license_key || 'YOUR-LICENSE-KEY')
|
||||
|
||||
const linuxCommands = computed(() => `# Download the agent
|
||||
curl -LO https://cdn.corrosionmgmt.com/companion/latest/corrosion-companion-linux-amd64
|
||||
chmod +x corrosion-companion-linux-amd64
|
||||
curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64
|
||||
chmod +x corrosion-host-agent-linux-amd64
|
||||
|
||||
# Start with your license key
|
||||
export LICENSE_ID="${licenseKey.value}"
|
||||
export NATS_URL="nats://nats.corrosionmgmt.com:4222"
|
||||
./corrosion-companion-linux-amd64`)
|
||||
./corrosion-host-agent-linux-amd64`)
|
||||
|
||||
const windowsCommands = computed(() => `# Requires PowerShell (not Command Prompt)
|
||||
# Download the agent
|
||||
Invoke-WebRequest -Uri "https://cdn.corrosionmgmt.com/companion/latest/corrosion-companion-windows-amd64.exe" -OutFile "corrosion-companion-windows-amd64.exe"
|
||||
Invoke-WebRequest -Uri "https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe" -OutFile "corrosion-host-agent-windows-amd64.exe"
|
||||
|
||||
# Start with your license key
|
||||
$env:LICENSE_ID="${licenseKey.value}"
|
||||
$env:NATS_URL="nats://nats.corrosionmgmt.com:4222"
|
||||
.\\corrosion-companion-windows-amd64.exe`)
|
||||
.\\corrosion-host-agent-windows-amd64.exe`)
|
||||
|
||||
async function copySetupCommands() {
|
||||
try {
|
||||
@@ -278,17 +314,18 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div class="sv">
|
||||
<!-- Page head -->
|
||||
<!-- Page head — game-aware identity -->
|
||||
<div class="sv__head">
|
||||
<div class="sv__head-id">
|
||||
<div class="sv__head-chip">
|
||||
<Icon name="server" :size="20" :stroke-width="2" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="t-eyebrow">Server management</div>
|
||||
<div class="t-eyebrow">{{ profile.label }} · Server management</div>
|
||||
<h1 class="sv__title">Server</h1>
|
||||
</div>
|
||||
</div>
|
||||
<Badge tone="neutral" :mono="true" class="sv__model-badge">{{ managementModelLabel }}</Badge>
|
||||
</div>
|
||||
|
||||
<!-- Connection -->
|
||||
@@ -350,8 +387,8 @@ onMounted(async () => {
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<!-- Companion agent -->
|
||||
<Panel title="Companion agent" subtitle="Bare-metal server management binary">
|
||||
<!-- Host agent -->
|
||||
<Panel title="Host agent" subtitle="Bare-metal server management binary">
|
||||
<template #actions>
|
||||
<Badge :tone="isAgentConnected ? 'online' : 'offline'" :dot="true" :pulse="isAgentConnected">
|
||||
{{ isAgentConnected ? 'Active' : 'Inactive' }}
|
||||
@@ -380,20 +417,20 @@ onMounted(async () => {
|
||||
<!-- Download -->
|
||||
<div class="sv__section-head">
|
||||
<Icon name="download" :size="14" />
|
||||
<span>Download companion agent</span>
|
||||
<span>Download host agent</span>
|
||||
</div>
|
||||
<div class="sv__downloads sv__mb">
|
||||
<a
|
||||
href="https://cdn.corrosionmgmt.com/companion/latest/corrosion-companion-linux-amd64"
|
||||
download="corrosion-companion-linux-amd64"
|
||||
href="https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64"
|
||||
download="corrosion-host-agent-linux-amd64"
|
||||
class="sv__dl-link"
|
||||
>
|
||||
<Icon name="download" :size="15" />
|
||||
Linux (amd64)
|
||||
</a>
|
||||
<a
|
||||
href="https://cdn.corrosionmgmt.com/companion/latest/corrosion-companion-windows-amd64.exe"
|
||||
download="corrosion-companion-windows-amd64.exe"
|
||||
href="https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe"
|
||||
download="corrosion-host-agent-windows-amd64.exe"
|
||||
class="sv__dl-link"
|
||||
>
|
||||
<Icon name="download" :size="15" />
|
||||
@@ -424,28 +461,28 @@ onMounted(async () => {
|
||||
<!-- Linux commands -->
|
||||
<div v-if="setupTab === 'linux'" class="sv__codeblock">
|
||||
<p class="sv__cmt"># Download the agent</p>
|
||||
<p>curl -LO https://cdn.corrosionmgmt.com/companion/latest/corrosion-companion-linux-amd64</p>
|
||||
<p>chmod +x corrosion-companion-linux-amd64</p>
|
||||
<p>curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64</p>
|
||||
<p>chmod +x corrosion-host-agent-linux-amd64</p>
|
||||
<p class="sv__cmt sv__mt"># Start with your license key</p>
|
||||
<p>export LICENSE_ID=<span class="sv__accent">"{{ licenseKey }}"</span></p>
|
||||
<p>export NATS_URL=<span class="sv__accent">"nats://nats.corrosionmgmt.com:4222"</span></p>
|
||||
<p>./corrosion-companion-linux-amd64</p>
|
||||
<p>./corrosion-host-agent-linux-amd64</p>
|
||||
</div>
|
||||
|
||||
<!-- Windows commands -->
|
||||
<div v-if="setupTab === 'windows'" class="sv__codeblock">
|
||||
<p class="sv__cmt"># Requires PowerShell (not Command Prompt)</p>
|
||||
<p class="sv__cmt"># Download the agent</p>
|
||||
<p>Invoke-WebRequest -Uri <span class="sv__accent">"https://cdn.corrosionmgmt.com/companion/latest/corrosion-companion-windows-amd64.exe"</span> -OutFile <span class="sv__accent">"corrosion-companion-windows-amd64.exe"</span></p>
|
||||
<p>Invoke-WebRequest -Uri <span class="sv__accent">"https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe"</span> -OutFile <span class="sv__accent">"corrosion-host-agent-windows-amd64.exe"</span></p>
|
||||
<p class="sv__cmt sv__mt"># Start with your license key</p>
|
||||
<p>$env:LICENSE_ID=<span class="sv__accent">"{{ licenseKey }}"</span></p>
|
||||
<p>$env:NATS_URL=<span class="sv__accent">"nats://nats.corrosionmgmt.com:4222"</span></p>
|
||||
<p>.\corrosion-companion-windows-amd64.exe</p>
|
||||
<p>.\corrosion-host-agent-windows-amd64.exe</p>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<!-- Deploy Rust Server -->
|
||||
<Panel title="Deploy Rust server" subtitle="One-click: SteamCMD, download, configure, start">
|
||||
<!-- Deploy Server — Rust only (SteamCMD path). Other games use docker-compose or external tooling. -->
|
||||
<Panel v-if="isRust" title="Deploy Rust server" subtitle="One-click: SteamCMD, download, configure, start">
|
||||
<template #title-append>
|
||||
<Icon name="rocket" :size="15" />
|
||||
</template>
|
||||
@@ -560,8 +597,28 @@ onMounted(async () => {
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<!-- Install Oxide / uMod -->
|
||||
<Panel title="Install Oxide / uMod" subtitle="Required for all plugins including CorrosionCompanion">
|
||||
<!-- Non-Rust: Docker-managed server note -->
|
||||
<Panel
|
||||
v-if="isDockerManaged"
|
||||
:title="profile.label + ' server deployment'"
|
||||
subtitle="Managed via Docker Compose"
|
||||
>
|
||||
<template #title-append>
|
||||
<Icon name="box" :size="15" />
|
||||
</template>
|
||||
<EmptyState
|
||||
icon="box"
|
||||
title="Docker-managed deployment"
|
||||
:description="profile.label + ' servers are managed via Docker Compose. Connect the host agent on your Docker host to enable lifecycle management.'"
|
||||
>
|
||||
<template #action>
|
||||
<Badge tone="info">Docker · Compose</Badge>
|
||||
</template>
|
||||
</EmptyState>
|
||||
</Panel>
|
||||
|
||||
<!-- Install Oxide / uMod — Rust only -->
|
||||
<Panel v-if="hasPluginSystem" title="Install Oxide / uMod" subtitle="Required for all plugins including CorrosionCompanion">
|
||||
<template #title-append>
|
||||
<Icon name="puzzle" :size="15" />
|
||||
</template>
|
||||
@@ -611,6 +668,79 @@ onMounted(async () => {
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<!-- Workshop Mods info — Conan / Soulmask (Steam Workshop, no install step needed) -->
|
||||
<Panel
|
||||
v-else-if="profile.mods === 'workshop'"
|
||||
:title="(profile.terminology.mods ?? 'Workshop Mods')"
|
||||
:subtitle="profile.label + ' uses Steam Workshop — no manual install step required'"
|
||||
>
|
||||
<template #title-append>
|
||||
<Icon name="layers" :size="15" />
|
||||
</template>
|
||||
<EmptyState
|
||||
icon="layers"
|
||||
:title="profile.label + ' mod management'"
|
||||
:description="profile.label + ' loads mods directly from Steam Workshop. Manage your mod list in server config — no Corrosion install step needed.'"
|
||||
/>
|
||||
</Panel>
|
||||
|
||||
<!-- Conan Exiles special concepts (Clans / Thralls / Purge) -->
|
||||
<Panel
|
||||
v-if="profile.accent === 'conan'"
|
||||
title="Conan Exiles concepts"
|
||||
subtitle="Key admin mechanics for Conan Exiles servers"
|
||||
>
|
||||
<div class="sv__concept-grid">
|
||||
<div class="sv__concept">
|
||||
<Icon name="users" :size="16" />
|
||||
<div>
|
||||
<div class="sv__concept-label">Clans</div>
|
||||
<div class="sv__concept-desc">Player factions. Clan management via in-game admin panel or RCON.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sv__concept">
|
||||
<Icon name="zap" :size="16" />
|
||||
<div>
|
||||
<div class="sv__concept-label">Thralls & Avatars</div>
|
||||
<div class="sv__concept-desc">Server-controlled NPCs and deity summons. Purge cycle managed via server settings.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sv__concept">
|
||||
<Icon name="shield" :size="16" />
|
||||
<div>
|
||||
<div class="sv__concept-label">Purge</div>
|
||||
<div class="sv__concept-desc">NPC raid events targeting player bases. Enable / tune via server config.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
<!-- Soulmask clustering section -->
|
||||
<Panel
|
||||
v-if="profile.clustering === 'main-client'"
|
||||
:title="clusterLabel"
|
||||
subtitle="Main-client cluster topology for Soulmask"
|
||||
>
|
||||
<EmptyState
|
||||
icon="network"
|
||||
title="Cluster management coming soon"
|
||||
:description="'Connect a ' + profile.label + ' host to manage the main-client cluster from this panel. Cluster configuration requires the host agent.'"
|
||||
/>
|
||||
</Panel>
|
||||
|
||||
<!-- Dune BattleGroup / Sietches section -->
|
||||
<Panel
|
||||
v-if="profile.clustering === 'battlegroup'"
|
||||
title="BattleGroups & Sietches"
|
||||
subtitle="Dune: Awakening server cluster topology"
|
||||
>
|
||||
<EmptyState
|
||||
icon="map"
|
||||
title="Sietch management requires a connected Dune host"
|
||||
description="Connect the host agent on your Dune: Awakening Docker host to manage BattleGroups and Sietches from this panel."
|
||||
/>
|
||||
</Panel>
|
||||
|
||||
<!-- Configuration -->
|
||||
<Panel title="Configuration">
|
||||
<template #actions>
|
||||
@@ -708,8 +838,13 @@ onMounted(async () => {
|
||||
</div>
|
||||
<div class="sv__toggle-row">
|
||||
<div class="sv__toggle-body">
|
||||
<div class="sv__toggle-label">Auto-update on force wipe</div>
|
||||
<div class="sv__toggle-sub">Update when Facepunch pushes</div>
|
||||
<div class="sv__toggle-label">
|
||||
<!-- Rust: "force wipe" is a Facepunch concept. Others: plain "auto-update" -->
|
||||
{{ isRust ? 'Auto-update on force wipe' : 'Auto-update on patch' }}
|
||||
</div>
|
||||
<div class="sv__toggle-sub">
|
||||
{{ isRust ? 'Update when Facepunch pushes' : 'Update when the developer pushes a patch' }}
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
:model-value="server.config?.auto_update_on_force_wipe ?? false"
|
||||
@@ -717,7 +852,8 @@ onMounted(async () => {
|
||||
@update:model-value="toggleAutomation('auto_update_on_force_wipe')"
|
||||
/>
|
||||
</div>
|
||||
<div class="sv__toggle-row">
|
||||
<!-- Rust-only: force wipe eligibility is a Facepunch concept -->
|
||||
<div v-if="isRust" class="sv__toggle-row">
|
||||
<div class="sv__toggle-body">
|
||||
<div class="sv__toggle-label">Force wipe eligible</div>
|
||||
<div class="sv__toggle-sub">Server participates in force wipes</div>
|
||||
@@ -848,4 +984,19 @@ onMounted(async () => {
|
||||
.sv__toggle-row:first-child { padding-top: 0; }
|
||||
.sv__toggle-label { font-size: var(--text-sm); font-weight: 500; color: var(--text-primary); }
|
||||
.sv__toggle-sub { font-size: var(--text-xs); color: var(--text-tertiary); margin-top: 2px; }
|
||||
|
||||
/* Management model badge in page head */
|
||||
.sv__model-badge { align-self: center; }
|
||||
|
||||
/* Game concept cards (Conan Exiles special features) */
|
||||
.sv__concept-grid { display: flex; flex-direction: column; gap: 14px; }
|
||||
.sv__concept {
|
||||
display: flex; align-items: flex-start; gap: 12px;
|
||||
padding: 12px 14px;
|
||||
background: var(--surface-raised); border-radius: var(--radius-md);
|
||||
box-shadow: var(--ring-default);
|
||||
color: var(--accent);
|
||||
}
|
||||
.sv__concept-label { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); margin-bottom: 2px; }
|
||||
.sv__concept-desc { font-size: var(--text-xs); color: var(--text-tertiary); line-height: 1.5; }
|
||||
</style>
|
||||
|
||||
@@ -35,7 +35,7 @@ function syncPorts() {
|
||||
}
|
||||
|
||||
const connectionTypes = [
|
||||
{ value: 'bare_metal', label: 'Bare metal / VPS', desc: 'Direct connection via Companion Agent' },
|
||||
{ value: 'bare_metal', label: 'Bare metal / VPS', desc: 'Direct connection via Corrosion host agent' },
|
||||
{ value: 'amp', label: 'AMP (CubeCoders)', desc: 'Connect through AMP panel API' },
|
||||
{ value: 'pterodactyl', label: 'Pterodactyl', desc: 'Connect through Pterodactyl panel API' },
|
||||
]
|
||||
@@ -183,7 +183,7 @@ async function completeSetup() {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Companion agent install -->
|
||||
<!-- Step 2: Corrosion host agent install -->
|
||||
<div v-if="step === 2" class="setup-card">
|
||||
<div class="setup-card__head setup-card__head--center">
|
||||
<div class="setup-icon">
|
||||
@@ -191,12 +191,12 @@ async function completeSetup() {
|
||||
<path d="M5 12.55a11 11 0 0 1 14.08 0M1.42 9a16 16 0 0 1 21.16 0M8.53 16.11a6 6 0 0 1 6.95 0M12 20h.01" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="setup-card__title">Install the Companion Agent</h1>
|
||||
<h1 class="setup-card__title">Install the Corrosion host agent</h1>
|
||||
<p class="setup-card__sub">The agent runs on your server and connects to Corrosion — no inbound ports required.</p>
|
||||
</div>
|
||||
|
||||
<div class="setup-code">
|
||||
<p class="setup-code__comment"># Download and install the Companion Agent</p>
|
||||
<p class="setup-code__comment"># Download and install the Corrosion host agent</p>
|
||||
<p class="setup-code__cmd">curl -sSL https://get.corrosionmgmt.com | sh</p>
|
||||
<p class="setup-code__comment setup-code__comment--mt"># Start the agent with your license key</p>
|
||||
<p class="setup-code__cmd">corrosion-agent start --key {{ auth.license?.license_key ?? 'YOUR-LICENSE-KEY' }}</p>
|
||||
|
||||
@@ -185,7 +185,7 @@ const mockActiveGame = activeGame
|
||||
<span class="g"><Icon name="box" :size="13" /></span>
|
||||
<span class="nm">
|
||||
Main · 2x Vanilla
|
||||
<small>asgard-01 · rust</small>
|
||||
<small>rust-host · rust</small>
|
||||
</span>
|
||||
<span class="st"><b />online</span>
|
||||
</div>
|
||||
@@ -193,7 +193,7 @@ const mockActiveGame = activeGame
|
||||
<span class="g"><Icon name="sun" :size="13" /></span>
|
||||
<span class="nm">
|
||||
Arrakis · Hardcore
|
||||
<small>asgard-01 · dune</small>
|
||||
<small>dune-host · dune</small>
|
||||
</span>
|
||||
<span class="st"><b />online</span>
|
||||
</div>
|
||||
@@ -201,7 +201,7 @@ const mockActiveGame = activeGame
|
||||
<span class="g"><Icon name="swords" :size="13" /></span>
|
||||
<span class="nm">
|
||||
Exiled Lands · PvP-C
|
||||
<small>asgard-02 · conan</small>
|
||||
<small>conan-host · conan</small>
|
||||
</span>
|
||||
<span class="st"><b />online</span>
|
||||
</div>
|
||||
|
||||
@@ -2,12 +2,18 @@ import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8'))
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
],
|
||||
define: {
|
||||
__APP_VERSION__: JSON.stringify(pkg.version),
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
|
||||
Reference in New Issue
Block a user