feat: computer statistics

This commit is contained in:
trafficlunar 2025-07-20 22:53:13 +01:00
parent c5598c6620
commit 03c0bb7b62
7 changed files with 376 additions and 33 deletions

View file

@ -0,0 +1,226 @@
<script lang="ts">
import { Area, Axis, Chart, Highlight, LinearGradient, Svg, Tooltip } from "layerchart";
import { scaleTime } from "d3-scale";
import { onMount } from "svelte";
interface ApiResponse {
online: boolean;
uptimeStart: number;
totals: {
keys: number;
clicks: number;
};
graph: {
timestamp: string;
cpu: number;
ram: number;
keys: number;
clicks: number;
}[];
}
type GraphData = {
timestamp: Date;
cpu: number;
ram: number;
keys: number;
clicks: number;
}[];
let online = $state(false);
let currentCPU = $state(0);
let currentRAM = $state(0);
let clicksPerMinute = $state(0);
let keysPerMinute = $state(0);
let uptime = $state("");
let allTimeClicks = $state(0);
let allTimeKeys = $state(0);
let chartData = $state<GraphData>([]);
onMount(() => {
let uptimeStart: Date;
const get = async () => {
const request = await fetch("https://api.trafficlunar.net/computer");
const data = (await request.json()) as ApiResponse;
online = data.online;
uptimeStart = new Date(data.uptimeStart * 1000); // convert to milliseconds
setUptime();
currentCPU = data.graph[data.graph.length - 1].cpu;
currentRAM = data.graph[data.graph.length - 1].ram;
allTimeClicks = data.totals.clicks;
allTimeKeys = data.totals.keys;
chartData = data.graph.map((point) => ({
...point,
timestamp: new Date(point.timestamp),
cpu: point.cpu / 100,
ram: point.ram / 100,
}));
const totalClicks = chartData.reduce((sum, point) => sum + point.clicks, 0);
const totalKeys = chartData.reduce((sum, point) => sum + point.keys, 0);
clicksPerMinute = totalClicks / 60; // 60 minutes (max graph data)
keysPerMinute = totalKeys / 60;
};
const setUptime = () => {
if (!uptimeStart) return;
const diff = Math.floor((Date.now() - uptimeStart.getTime()) / 1000);
const hrs = String(Math.floor(diff / 3600)).padStart(2, "0");
const mins = String(Math.floor((diff % 3600) / 60)).padStart(2, "0");
const secs = String(diff % 60).padStart(2, "0");
uptime = `${hrs}:${mins}:${secs}`;
};
get();
setUptime();
const interval = setInterval(get, 30000);
const uptimeInterval = setInterval(setUptime, 1000);
return () => {
clearInterval(interval);
clearInterval(uptimeInterval);
};
});
</script>
<div class="grid grid-cols-1 md:grid-cols-2 gap-0.5 font-mono text-sm p-4">
<div class="grid grid-cols-2 grid-rows-4 gap-0.5">
<span class="font-black">CPU:</span>
<span>{currentCPU}%</span>
<span class="font-black">RAM:</span>
<span>{currentRAM}%</span>
<span class="font-black">Clicks:</span>
<span>{clicksPerMinute.toFixed(2)}/min</span>
<span class="font-black">Keys:</span>
<span>{keysPerMinute.toFixed(2)}/min</span>
</div>
<div class="grid grid-cols-2 grid-rows-4 gap-0.5">
<span class="font-black">Uptime:</span>
<span>{uptime}</span>
<span></span>
<span></span>
<span class="font-black">All Time Clicks:</span>
<span>{allTimeClicks}</span>
<span class="font-black">All Time Keys:</span>
<span>{allTimeKeys}</span>
</div>
</div>
<div class="relative h-96">
<div class="absolute inset-0 size-full rounded-md bg-mantle border border-surface0 z-10"></div>
<div class="flex justify-center items-center absolute -top-2 right-2 z-0">
<div class="w-4 h-4 rounded-full {online ? 'bg-green' : 'bg-red'}"></div>
<div class="w-4 h-4 rounded-full absolute animate-duration-2s animate-delay-2s {online ? 'bg-green animate-ping' : 'bg-red'}"></div>
</div>
<div class="absolute inset-0 size-full z-20 p-2">
<Chart
data={chartData}
x="timestamp"
xScale={scaleTime()}
y={["cpu", "ram"]}
yDomain={[0, 1]}
yNice
padding={{ left: 32, top: 8, right: 12, bottom: 20 }}
tooltip={{ mode: "bisect-x" }}
>
<Svg>
<Axis placement="left" grid rule classes={{ tickLabel: "fill-overlay1 font-medium" }} format="percentRound" />
<Axis placement="bottom" rule classes={{ tickLabel: "fill-overlay1 font-medium" }} />
<!-- RAM -->
<LinearGradient class="from-peach/50 to-peach/1" vertical>
{#snippet children({ gradient })}
<Area y1={(d) => d.ram} fill={gradient} line={{ class: "stroke-2 stroke-peach" }} />
{/snippet}
</LinearGradient>
<!-- CPU -->
<LinearGradient class="from-yellow/50 to-yellow/1" vertical>
{#snippet children({ gradient })}
<Area y1={(d) => d.cpu} fill={gradient} line={{ class: "stroke-2 stroke-yellow" }} />
{/snippet}
</LinearGradient>
<Highlight lines={{ class: "stroke-peach [stroke-dasharray:unset]" }} />
<Highlight y={(d) => d.ram} points={{ class: "fill-peach" }} />
<Highlight y={(d) => d.cpu} points={{ class: "fill-yellow" }} />
</Svg>
<Tooltip.Root class="border border-peach bg-peach/10 text-white/85 backdrop-blur-lg">
{#snippet children({ data })}
<Tooltip.Header value={data.timestamp} format="time" classes={{ root: "border-peach" }} />
<Tooltip.List>
<Tooltip.Item label="RAM (%)" value={data.ram} format="percentRound" />
<Tooltip.Item label="CPU (%)" value={data.cpu} format="percentRound" />
</Tooltip.List>
{/snippet}
</Tooltip.Root>
</Chart>
</div>
</div>
<div class="relative h-48 rounded-md bg-mantle border border-surface0 p-2 mt-2">
<Chart
data={chartData}
x="timestamp"
xScale={scaleTime()}
y={["clicks", "keys"]}
yDomain={[0, null]}
yNice
padding={{ left: 32, top: 8, right: 12, bottom: 20 }}
tooltip={{ mode: "bisect-x" }}
>
<Svg>
<Axis placement="left" rule classes={{ tickLabel: "fill-overlay1 font-medium" }} />
<Axis placement="bottom" rule classes={{ tickLabel: "fill-overlay1 font-medium" }} />
<!-- Keys -->
<LinearGradient class="from-green/50 to-green/1" vertical>
{#snippet children({ gradient })}
<Area y1={(d) => d.keys} fill={gradient} line={{ class: "stroke-2 stroke-green" }} />
{/snippet}
</LinearGradient>
<!-- Clicks -->
<LinearGradient class="from-teal/50 to-teal/1" vertical>
{#snippet children({ gradient })}
<Area y1={(d) => d.clicks} fill={gradient} line={{ class: "stroke-2 stroke-teal" }} />
{/snippet}
</LinearGradient>
<Highlight lines={{ class: "stroke-peach [stroke-dasharray:unset]" }} />
<Highlight y={(d) => d.keys} points={{ class: "fill-green" }} />
<Highlight y={(d) => d.clicks} points={{ class: "fill-teal" }} />
</Svg>
<Tooltip.Root class="border border-peach bg-peach/10 text-white/85 backdrop-blur-lg">
{#snippet children({ data })}
<Tooltip.Header value={data.timestamp} format="time" classes={{ root: "border-peach" }} />
<Tooltip.List>
<Tooltip.Item label="Keys" value={data.keys} />
<Tooltip.Item label="Clicks" value={data.clicks} />
</Tooltip.List>
{/snippet}
</Tooltip.Root>
</Chart>
</div>

View file

@ -26,8 +26,8 @@
});
</script>
<a href={url} class="block relative h-20 group">
<div class="absolute size-full rounded-md flex gap-2 bg-mantle border border-surface0 z-10"></div>
<a href={url} class="block relative h-24 group">
<div class="absolute size-full rounded-md bg-mantle border border-surface0 z-10"></div>
<div class="flex justify-center items-center absolute -top-2 right-2 z-0">
<div class="w-4 h-4 rounded-full {playing ? 'bg-green' : 'bg-red'}"></div>
@ -35,7 +35,7 @@
</div>
<div class="absolute p-2 flex gap-2 z-30 w-full">
<img src={image} alt="album cover" class="size-16 rounded-full animate-spin animate-duration-30s" />
<img src={image} alt="album cover" class="size-20 rounded-full animate-spin animate-duration-30s" />
<div class="flex flex-col w-full min-w-0">
<h1 class="font-medium">{song}</h1>

View file

@ -15,7 +15,7 @@ import LocationIcon from "../assets/icons/location.svg";
<p>
Hey there, I'm <strong class="text-peach">trafficlunar</strong>. I'm a <Age client:only /> year old self-proclaimed full-stack developer and sysadmin.
</p>
<p class="my-2">You can view what I'm currently doing by going <a href="/status" class="link">here</a>.</p>
<p class="my-2">You can view what I'm currently listening to and my computer statistics by going <a href="/status" class="link">here</a>.</p>
<div class="flex gap-1 mt-4">
<div class="text-sm rounded bg-peach/25 text-peach w-fit pl-1 pr-1.5 py-0.5 flex gap-1 items-center">

View file

@ -2,19 +2,25 @@
import Layout from "../layouts/Layout.astro";
import Music from "../components/Music.svelte";
import Computer from "../components/Computer.svelte";
---
<Layout>
<section class="!mb-0 !pt-3">
<legend>status</legend>
<div class="flex items-center gap-4 text-subtext0 text-sm font-medium mb-2">
<div class="flex items-center gap-4 text-subtext0 text-sm font-medium mb-4">
<hr class="flex-grow border-surface1" />
<span>last.fm</span>
<hr class="flex-grow border-surface1" />
</div>
<Music client:only />
<p class="text-center mt-4 text-subtext1 text-sm">more coming soon...</p>
<div class="flex items-center gap-4 text-subtext0 text-sm font-medium mt-4">
<hr class="flex-grow border-surface1" />
<span>computer</span>
<hr class="flex-grow border-surface1" />
</div>
<Computer client:only />
</section>
</Layout>

View file

@ -1,4 +1,5 @@
@import "tailwindcss";
@source '../node_modules/layerchart/dist';
@theme {
--font-sans: "Inter Variable", "sans-serif";