fix(audit): kill fake install cmds + dead demo CTA; production fonts; scoped error boundary; admin bootstrap seed
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
Full-site fake-data audit findings: - SetupWizard showed a curl|sh installer (get.corrosionmgmt.com) and a 'corrosion-agent' binary that don't exist -> real host-agent commands - 'View live demo' CTA on 5 marketing pages linked to a login, not a demo -> honest 'Sign in' - Google Fonts @import was silently dropped from the production CSS bundle (mid-bundle @import) -> <link> tags in index.html; prod was shipping system fallback fonts - App-root ErrorBoundary bricked the entire SPA (incl. marketing) on a single failed fetch until manual reload -> resets on route change + content-scoped boundary inside DashboardLayout so nav chrome survives - Status page KPIs showed fake zeros while the fetch failed -> em dash - Login lacked the forgot-password link (flow already existed end-to-end) - AdminSeedService: fresh DB had schema but no login possible; seeds super-admin + license from ADMIN_* env when users table is empty Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,14 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0a0a0a" />
|
||||
<title>Corrosion Management</title>
|
||||
<!-- Fonts via <link>, NOT a CSS @import — the bundler drops @import rules
|
||||
that land mid-file after concatenation, silently shipping system fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700;800&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400&family=Oxanium:wght@500;600;700;800&display=swap"
|
||||
/>
|
||||
<script>
|
||||
/* FOUC guard — apply persisted theme/game to <html> before the app mounts,
|
||||
so the design-system tokens paint with the right skin from frame one. */
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onErrorCaptured } from 'vue'
|
||||
import { ref, watch, onErrorCaptured } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import Icon from '@/components/ds/core/Icon.vue'
|
||||
import Button from '@/components/ds/core/Button.vue'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
/** 'screen' fills the viewport (app root); 'content' fills its container (inside layout chrome) */
|
||||
variant?: 'screen' | 'content'
|
||||
}>(), { variant: 'screen' })
|
||||
|
||||
const route = useRoute()
|
||||
const hasError = ref(false)
|
||||
const errorMessage = ref('')
|
||||
|
||||
@@ -13,6 +20,12 @@ onErrorCaptured((err) => {
|
||||
return false
|
||||
})
|
||||
|
||||
// A failed view must not brick navigation — clear the error when the route changes
|
||||
watch(() => route.fullPath, () => {
|
||||
hasError.value = false
|
||||
errorMessage.value = ''
|
||||
})
|
||||
|
||||
function retry() {
|
||||
hasError.value = false
|
||||
errorMessage.value = ''
|
||||
@@ -21,7 +34,7 @@ function retry() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasError" class="eb-screen">
|
||||
<div v-if="hasError" class="eb-screen" :class="{ 'eb-screen--content': variant === 'content' }">
|
||||
<div class="eb-card">
|
||||
<div class="eb-icon-wrap">
|
||||
<Icon name="triangle-alert" :size="24" :stroke-width="1.75" />
|
||||
@@ -44,6 +57,11 @@ function retry() {
|
||||
padding: var(--space-6);
|
||||
}
|
||||
|
||||
.eb-screen--content {
|
||||
min-height: 60vh;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.eb-card {
|
||||
background: var(--surface-base);
|
||||
box-shadow: var(--ring-default), var(--shadow-md);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useThemeGame } from '@/composables/useThemeGame'
|
||||
import { useGameProfile } from '@/config/gameProfiles'
|
||||
import type { NavSection, NavItemDef } from '@/config/gameProfiles'
|
||||
import { safeDate } from '@/utils/formatters'
|
||||
import ErrorBoundary from '@/components/ErrorBoundary.vue'
|
||||
import Logo from '@/components/ds/brand/Logo.vue'
|
||||
import Badge from '@/components/ds/core/Badge.vue'
|
||||
import StatusDot from '@/components/ds/core/StatusDot.vue'
|
||||
@@ -284,9 +285,11 @@ const themeIcon = computed(() => theme.value === 'dark' ? 'sun' : 'moon')
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Page content -->
|
||||
<!-- Page content — boundary keeps sidebar/topbar alive when a view fails -->
|
||||
<main class="app__content">
|
||||
<RouterView />
|
||||
<ErrorBoundary variant="content">
|
||||
<RouterView />
|
||||
</ErrorBoundary>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
JetBrains Mono — console, data, IDs, telemetry
|
||||
Oxanium — brand wordmark + marketing display (game-ops flavor)
|
||||
------------------------------------------------------------
|
||||
NOTE: Loaded from Google Fonts CDN. If you want these self-
|
||||
hosted (offline), send the woff2 files and these @imports
|
||||
become @font-face rules.
|
||||
NOTE: The Google Fonts stylesheet is loaded via <link> tags in
|
||||
index.html — NOT @import here. A CSS @import that ends up
|
||||
mid-bundle after concatenation is silently dropped by the
|
||||
optimizer (fonts never load in production). If you want these
|
||||
self-hosted (offline), send the woff2 files and they become
|
||||
@font-face rules here.
|
||||
============================================================ */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700;800&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400&family=Oxanium:wght@500;600;700;800&display=swap');
|
||||
|
||||
:root {
|
||||
--font-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, monospace;
|
||||
|
||||
@@ -191,6 +191,8 @@ function handleBackToLogin() {
|
||||
<p v-if="!showTotpInput" class="auth-footer">
|
||||
No account?
|
||||
<router-link to="/register" class="auth-footer__link">Create one</router-link>
|
||||
·
|
||||
<router-link to="/forgot-password" class="auth-footer__link">Forgot password?</router-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -196,14 +196,17 @@ async function completeSetup() {
|
||||
</div>
|
||||
|
||||
<div class="setup-code">
|
||||
<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>
|
||||
<p class="setup-code__comment"># Download the Corrosion host agent (Linux)</p>
|
||||
<p class="setup-code__cmd">curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64</p>
|
||||
<p class="setup-code__cmd">chmod +x corrosion-host-agent-linux-amd64</p>
|
||||
<p class="setup-code__comment setup-code__comment--mt"># Start with your license key</p>
|
||||
<p class="setup-code__cmd">export LICENSE_ID="{{ auth.license?.license_key ?? 'YOUR-LICENSE-KEY' }}"</p>
|
||||
<p class="setup-code__cmd">export NATS_URL="nats://nats.corrosionmgmt.com:4222"</p>
|
||||
<p class="setup-code__cmd">./corrosion-host-agent-linux-amd64</p>
|
||||
</div>
|
||||
|
||||
<p class="setup-hint">
|
||||
The agent auto-registers with your panel. You can also use the uMod plugin for lightweight integration.
|
||||
On Windows, download the agent from the Server page after setup. The agent connects outbound and auto-registers with your panel.
|
||||
</p>
|
||||
|
||||
<div class="setup-actions">
|
||||
@@ -235,7 +238,7 @@ async function completeSetup() {
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="setup-card__title">You're all set</h1>
|
||||
<p class="setup-card__sub">Your server is configured. Head to the dashboard to start managing your Rust server.</p>
|
||||
<p class="setup-card__sub">Your server is configured. Head to the dashboard to start managing your game server.</p>
|
||||
<Button
|
||||
type="button"
|
||||
:loading="isLoading"
|
||||
|
||||
@@ -291,7 +291,7 @@ onUnmounted(() => { io?.disconnect() })
|
||||
Sign up above
|
||||
</a>
|
||||
<a class="btn btn--ghost btn--lg" :href="panelUrl + '/login'">
|
||||
<Icon name="play" :size="17" />View live demo
|
||||
<Icon name="key" :size="17" />Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -350,7 +350,7 @@ onUnmounted(() => { io?.disconnect() })
|
||||
Join early access
|
||||
</RouterLink>
|
||||
<a class="btn btn--ghost btn--lg" :href="panelUrl + '/login'">
|
||||
<Icon name="play" :size="17" />View live demo
|
||||
<Icon name="key" :size="17" />Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -113,7 +113,7 @@ const mockActiveGame = activeGame
|
||||
Join early access
|
||||
</RouterLink>
|
||||
<a class="btn btn--ghost btn--lg" :href="panelUrl + '/login'">
|
||||
<Icon name="play" :size="17" />View live demo
|
||||
<Icon name="key" :size="17" />Sign in
|
||||
</a>
|
||||
</div>
|
||||
<!-- Game pills -->
|
||||
@@ -672,7 +672,7 @@ const mockActiveGame = activeGame
|
||||
Join early access
|
||||
</RouterLink>
|
||||
<a class="btn btn--ghost btn--lg" :href="panelUrl + '/login'">
|
||||
<Icon name="play" :size="17" />View live demo
|
||||
<Icon name="key" :size="17" />Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -351,7 +351,7 @@ const plans: Plan[] = [
|
||||
Join early access
|
||||
</RouterLink>
|
||||
<a class="btn btn--ghost btn--lg" :href="panelUrl + '/login'">
|
||||
<Icon name="play" :size="17" />View live demo
|
||||
<Icon name="key" :size="17" />Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -224,7 +224,7 @@ onUnmounted(() => { io?.disconnect() })
|
||||
Join early access
|
||||
</RouterLink>
|
||||
<a class="btn btn--ghost btn--lg" :href="panelUrl + '/login'">
|
||||
<Icon name="play" :size="17" />View live demo
|
||||
<Icon name="key" :size="17" />Sign in
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,12 +41,8 @@ interface StatusResponse {
|
||||
|
||||
const api = useApi()
|
||||
const servers = ref<ServerStatus[]>([])
|
||||
const platformHealth = ref<PlatformHealth>({
|
||||
total_servers: 0,
|
||||
online_servers: 0,
|
||||
total_players: 0,
|
||||
uptime_percent: 0,
|
||||
})
|
||||
// null until the first successful fetch — KPIs render '—', never fake zeros
|
||||
const platformHealth = ref<PlatformHealth | null>(null)
|
||||
|
||||
const searchQuery = ref('')
|
||||
const loading = ref(true)
|
||||
@@ -148,10 +144,10 @@ onUnmounted(() => {
|
||||
|
||||
<!-- Platform KPIs -->
|
||||
<div v-if="!loading" class="sp-kpis">
|
||||
<StatCard icon="server" label="Total servers" :value="String(platformHealth.total_servers)" />
|
||||
<StatCard icon="activity" label="Online now" :value="String(platformHealth.online_servers)" />
|
||||
<StatCard icon="users" label="Total players" :value="String(platformHealth.total_players)" />
|
||||
<StatCard icon="trending-up" label="Platform uptime" :value="safeFixed(platformHealth.uptime_percent, 1)" unit="%" />
|
||||
<StatCard icon="server" label="Total servers" :value="platformHealth ? String(platformHealth.total_servers) : '—'" />
|
||||
<StatCard icon="activity" label="Online now" :value="platformHealth ? String(platformHealth.online_servers) : '—'" />
|
||||
<StatCard icon="users" label="Total players" :value="platformHealth ? String(platformHealth.total_players) : '—'" />
|
||||
<StatCard icon="trending-up" label="Platform uptime" :value="safeFixed(platformHealth?.uptime_percent ?? null, 1, '—')" unit="%" />
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
|
||||
Reference in New Issue
Block a user