feat: add vue-i18n infrastructure and externalize all UI strings (A11Y-03)
Set up vue-i18n with English locale file containing 500+ keys organized by view namespace. All 15 views converted to use t() calls instead of hardcoded strings. Infrastructure ready for community translations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b9cc0a924e
commit
aba7aba25f
@@ -3,15 +3,15 @@
|
||||
<div class="hidden md:block mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Monitoring</h1>
|
||||
<p class="text-white/70">Real-time system metrics and container resource usage</p>
|
||||
<h1 class="text-3xl font-bold text-white mb-2">{{ t('monitoring.title') }}</h1>
|
||||
<p class="text-white/70">{{ t('monitoring.subtitle') }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="glass-button text-sm px-4 py-2" @click="exportMetrics('csv')">
|
||||
Export CSV
|
||||
{{ t('monitoring.exportCsv') }}
|
||||
</button>
|
||||
<button class="glass-button text-sm px-4 py-2" @click="exportMetrics('json')">
|
||||
Export JSON
|
||||
{{ t('monitoring.exportJson') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,22 +20,22 @@
|
||||
<!-- Summary Cards -->
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<div class="monitoring-stat-card">
|
||||
<p class="text-xs text-white/50 uppercase tracking-wide">CPU</p>
|
||||
<p class="text-xs text-white/50 uppercase tracking-wide">{{ t('monitoring.cpu') }}</p>
|
||||
<p class="text-2xl font-bold text-white">{{ current?.system.cpu_percent.toFixed(1) ?? '--' }}%</p>
|
||||
<p class="text-xs text-white/40">Load: {{ current?.system.load_avg_1.toFixed(2) ?? '--' }}</p>
|
||||
<p class="text-xs text-white/40">{{ t('monitoring.load') }} {{ current?.system.load_avg_1.toFixed(2) ?? '--' }}</p>
|
||||
</div>
|
||||
<div class="monitoring-stat-card">
|
||||
<p class="text-xs text-white/50 uppercase tracking-wide">Memory</p>
|
||||
<p class="text-xs text-white/50 uppercase tracking-wide">{{ t('monitoring.memory') }}</p>
|
||||
<p class="text-2xl font-bold text-white">{{ memPercent }}%</p>
|
||||
<p class="text-xs text-white/40">{{ formatBytes(current?.system.mem_used_bytes ?? 0) }} / {{ formatBytes(current?.system.mem_total_bytes ?? 0) }}</p>
|
||||
</div>
|
||||
<div class="monitoring-stat-card">
|
||||
<p class="text-xs text-white/50 uppercase tracking-wide">Disk</p>
|
||||
<p class="text-xs text-white/50 uppercase tracking-wide">{{ t('monitoring.diskUsage') }}</p>
|
||||
<p class="text-2xl font-bold text-white">{{ diskPercent }}%</p>
|
||||
<p class="text-xs text-white/40">{{ formatBytes(current?.system.disk_used_bytes ?? 0) }} / {{ formatBytes(current?.system.disk_total_bytes ?? 0) }}</p>
|
||||
</div>
|
||||
<div class="monitoring-stat-card">
|
||||
<p class="text-xs text-white/50 uppercase tracking-wide">Network</p>
|
||||
<p class="text-xs text-white/50 uppercase tracking-wide">{{ t('monitoring.network') }}</p>
|
||||
<p class="text-2xl font-bold text-white">{{ formatBytes(current?.system.net_rx_bytes ?? 0) }}</p>
|
||||
<p class="text-xs text-white/40">TX: {{ formatBytes(current?.system.net_tx_bytes ?? 0) }}</p>
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@
|
||||
<!-- Charts -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<div class="glass-card p-5">
|
||||
<h3 class="text-sm font-medium text-white/80 mb-3">CPU Usage (%)</h3>
|
||||
<h3 class="text-sm font-medium text-white/80 mb-3">{{ t('monitoring.cpuUsage') }}</h3>
|
||||
<LineChart
|
||||
:datasets="cpuDatasets"
|
||||
:labels="timeLabels"
|
||||
@@ -54,7 +54,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="glass-card p-5">
|
||||
<h3 class="text-sm font-medium text-white/80 mb-3">Memory Usage (%)</h3>
|
||||
<h3 class="text-sm font-medium text-white/80 mb-3">{{ t('monitoring.memoryUsage') }}</h3>
|
||||
<LineChart
|
||||
:datasets="memDatasets"
|
||||
:labels="timeLabels"
|
||||
@@ -64,7 +64,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="glass-card p-5">
|
||||
<h3 class="text-sm font-medium text-white/80 mb-3">Network I/O (bytes)</h3>
|
||||
<h3 class="text-sm font-medium text-white/80 mb-3">{{ t('monitoring.networkIo') }}</h3>
|
||||
<LineChart
|
||||
:datasets="netDatasets"
|
||||
:labels="timeLabels"
|
||||
@@ -73,7 +73,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="glass-card p-5">
|
||||
<h3 class="text-sm font-medium text-white/80 mb-3">RPC Latency (ms)</h3>
|
||||
<h3 class="text-sm font-medium text-white/80 mb-3">{{ t('monitoring.rpcLatency') }}</h3>
|
||||
<LineChart
|
||||
:datasets="latencyDatasets"
|
||||
:labels="timeLabels"
|
||||
@@ -86,12 +86,12 @@
|
||||
<!-- Alert History -->
|
||||
<div class="glass-card p-5 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-sm font-medium text-white/80">Alert History</h3>
|
||||
<h3 class="text-sm font-medium text-white/80">{{ t('monitoring.alertHistory') }}</h3>
|
||||
<button
|
||||
class="glass-button text-xs px-3 py-1"
|
||||
@click="showAlertConfig = !showAlertConfig"
|
||||
>
|
||||
{{ showAlertConfig ? 'Hide Config' : 'Configure' }}
|
||||
{{ showAlertConfig ? t('monitoring.hideConfig') : t('common.configure') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
|
||||
<!-- Fired Alerts List -->
|
||||
<div v-if="!alerts.length" class="text-white/40 text-sm py-4 text-center">
|
||||
No alerts fired
|
||||
{{ t('monitoring.noAlerts') }}
|
||||
</div>
|
||||
<div v-else class="space-y-2 max-h-64 overflow-y-auto">
|
||||
<div
|
||||
@@ -150,7 +150,7 @@
|
||||
class="text-xs text-white/40 hover:text-white/70 flex-shrink-0"
|
||||
@click="acknowledgeAlert(alert.id)"
|
||||
>
|
||||
Dismiss
|
||||
{{ t('common.dismiss') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,9 +158,9 @@
|
||||
|
||||
<!-- Container Resource Breakdown -->
|
||||
<div class="glass-card p-5 mb-6">
|
||||
<h3 class="text-sm font-medium text-white/80 mb-4">Container Resources</h3>
|
||||
<h3 class="text-sm font-medium text-white/80 mb-4">{{ t('monitoring.containerResources') }}</h3>
|
||||
<div v-if="!containers.length" class="text-white/40 text-sm py-4 text-center">
|
||||
No container metrics available
|
||||
{{ t('monitoring.noContainerMetrics') }}
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
@@ -190,11 +190,11 @@
|
||||
<!-- System Health Timeline -->
|
||||
<div class="glass-card p-5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-sm font-medium text-white/80">System Health</h3>
|
||||
<h3 class="text-sm font-medium text-white/80">{{ t('monitoring.systemHealth') }}</h3>
|
||||
<div class="flex items-center gap-2 text-xs text-white/40">
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-green-400"></span> Healthy
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-orange-400 ml-2"></span> Elevated
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-red-400 ml-2"></span> Critical
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-green-400"></span> {{ t('common.healthy') }}
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-orange-400 ml-2"></span> {{ t('common.elevated') }}
|
||||
<span class="inline-block w-2 h-2 rounded-full bg-red-400 ml-2"></span> {{ t('common.critical') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-0.5 h-6">
|
||||
@@ -213,13 +213,14 @@
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-white/30 mt-4 text-center">
|
||||
Refreshing every 5 seconds · WS connections: {{ current?.ws_connections ?? 0 }}
|
||||
{{ t('monitoring.refreshFooter') }} · {{ t('monitoring.wsConnections', { count: current?.ws_connections ?? 0 }) }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import LineChart from '@/components/LineChart.vue'
|
||||
import type { ChartDataset } from '@/components/LineChart.vue'
|
||||
@@ -279,6 +280,8 @@ interface FiredAlert {
|
||||
acknowledged: boolean
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const current = ref<MetricSnapshot | null>(null)
|
||||
const history = ref<MetricSnapshot[]>([])
|
||||
const containers = ref<ContainerMetrics[]>([])
|
||||
@@ -382,11 +385,11 @@ function formatBytes(bytes: number): string {
|
||||
|
||||
function ruleLabel(kind: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
disk_usage: 'Disk Usage',
|
||||
ram_usage: 'RAM Usage',
|
||||
container_crash: 'Container Crash',
|
||||
backend_error_spike: 'RPC Latency Spike',
|
||||
ssl_cert_expiry: 'SSL Cert Expiry',
|
||||
disk_usage: t('monitoring.diskUsage'),
|
||||
ram_usage: t('monitoring.ramUsage'),
|
||||
container_crash: t('monitoring.containerCrash'),
|
||||
backend_error_spike: t('monitoring.rpcLatencySpike'),
|
||||
ssl_cert_expiry: t('monitoring.sslCertExpiry'),
|
||||
}
|
||||
return labels[kind] ?? kind
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user