wraith/src/components/terminal/MonitorBar.vue
Vantz Stockwell 10dc3f9cbe
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m47s
fix: synchronous ToolWindow import + bars to 48px/16px
Tool windows (still closing instantly after every prior fix):
- Changed ToolWindow from defineAsyncComponent to direct synchronous
  import. All 14 tool components now bundled into the main JS chunk.
  Eliminates async chunk loading as a failure point — if the main
  bundle loads (which it does, since the main window works), the
  tool window code is guaranteed to be available.
- ToolWindow chunk no longer exists as a separate file

Status bar + Monitor bar:
- Both set to h-[48px] text-base px-6 (48px height, 16px text)
- Matching sizes for visual consistency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:35:39 -04:00

98 lines
3.0 KiB
Vue

<template>
<div
v-if="stats"
class="flex items-center gap-4 px-6 h-[48px] bg-[var(--wraith-bg-tertiary)] border-t border-[var(--wraith-border)] text-base font-mono shrink-0 select-none"
>
<!-- CPU -->
<span class="flex items-center gap-1">
<span class="text-[var(--wraith-text-muted)]">CPU</span>
<span :class="colorClass(stats.cpuPercent, 50, 80)">{{ stats.cpuPercent.toFixed(0) }}%</span>
</span>
<!-- RAM -->
<span class="flex items-center gap-1">
<span class="text-[var(--wraith-text-muted)]">RAM</span>
<span :class="colorClass(stats.memPercent, 50, 80)">{{ stats.memUsedMb }}M/{{ stats.memTotalMb }}M ({{ stats.memPercent.toFixed(0) }}%)</span>
</span>
<!-- Disk -->
<span class="flex items-center gap-1">
<span class="text-[var(--wraith-text-muted)]">DISK</span>
<span :class="colorClass(stats.diskPercent, 70, 90)">{{ stats.diskUsedGb.toFixed(0) }}G/{{ stats.diskTotalGb.toFixed(0) }}G ({{ stats.diskPercent.toFixed(0) }}%)</span>
</span>
<!-- Network -->
<span class="flex items-center gap-1">
<span class="text-[var(--wraith-text-muted)]">NET</span>
<span class="text-[var(--wraith-text-secondary)]">{{ formatBytes(stats.netRxBytes) }}↓ {{ formatBytes(stats.netTxBytes) }}↑</span>
</span>
<!-- OS -->
<span class="text-[var(--wraith-text-muted)] ml-auto">{{ stats.osType }}</span>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
const props = defineProps<{
sessionId: string;
}>();
interface SystemStats {
cpuPercent: number;
memUsedMb: number;
memTotalMb: number;
memPercent: number;
diskUsedGb: number;
diskTotalGb: number;
diskPercent: number;
netRxBytes: number;
netTxBytes: number;
osType: string;
}
const stats = ref<SystemStats | null>(null);
let unlistenFn: UnlistenFn | null = null;
let subscribeGeneration = 0;
function colorClass(value: number, warnThreshold: number, critThreshold: number): string {
if (value >= critThreshold) return "text-[#f85149]"; // red
if (value >= warnThreshold) return "text-[#e3b341]"; // amber
return "text-[#3fb950]"; // green
}
function formatBytes(bytes: number): string {
if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(1) + "G";
if (bytes >= 1048576) return (bytes / 1048576).toFixed(1) + "M";
if (bytes >= 1024) return (bytes / 1024).toFixed(0) + "K";
return bytes + "B";
}
async function subscribe(): Promise<void> {
const gen = ++subscribeGeneration;
if (unlistenFn) unlistenFn();
const fn = await listen<SystemStats>(`ssh:monitor:${props.sessionId}`, (event) => {
stats.value = event.payload;
});
if (gen !== subscribeGeneration) {
// A newer subscribe() call has already taken over — discard this listener
fn();
return;
}
unlistenFn = fn;
}
onMounted(subscribe);
watch(() => props.sessionId, () => {
stats.value = null;
subscribe();
});
onBeforeUnmount(() => {
if (unlistenFn) unlistenFn();
});
</script>