Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 15s
Remote monitoring bar: - Slim 24px bar at bottom of every SSH terminal - CPU, RAM, disk, network stats polled every 5s via exec channel - Cross-platform: Linux (/proc), macOS (vm_stat/sysctl), FreeBSD - Color-coded thresholds: green/amber/red - No agent installation — standard POSIX commands only SFTP follows active tab: - Added :key="activeSessionId" to FileTree component - Vue recreates FileTree when session changes, reinitializing SFTP CWD tracking fix (macOS + all platforms): - Old approach: exec channel pwd — returns HOME, not actual CWD - New approach: passive OSC 7 parsing in the output stream - Scans for \e]7;file://host/path\a without modifying data - Works with bash, zsh, fish on both Linux and macOS - Zero corruption risk — data passes through unmodified - Includes URL percent-decoding for paths with spaces Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
90 lines
2.8 KiB
Vue
90 lines
2.8 KiB
Vue
<template>
|
|
<div
|
|
v-if="stats"
|
|
class="flex items-center gap-4 px-3 h-6 bg-[var(--wraith-bg-tertiary)] border-t border-[var(--wraith-border)] text-[10px] 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;
|
|
|
|
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> {
|
|
if (unlistenFn) unlistenFn();
|
|
unlistenFn = await listen<SystemStats>(`ssh:monitor:${props.sessionId}`, (event) => {
|
|
stats.value = event.payload;
|
|
});
|
|
}
|
|
|
|
onMounted(subscribe);
|
|
|
|
watch(() => props.sessionId, () => {
|
|
stats.value = null;
|
|
subscribe();
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
if (unlistenFn) unlistenFn();
|
|
});
|
|
</script>
|