Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled

This commit is contained in:
drjones
2026-07-04 09:31:23 +00:00
commit 3678b199d0
154 changed files with 21714 additions and 0 deletions

43
web/src/lib/auth.test.ts Normal file
View File

@@ -0,0 +1,43 @@
import { describe, expect, it, beforeEach } from "vitest";
import {
clearStoredAuth,
getBasicAuthHeader,
getStoredAuth,
isAuthenticated,
setStoredAuth,
} from "./auth";
describe("auth session", () => {
beforeEach(() => {
sessionStorage.clear();
});
it("stores and retrieves credentials from sessionStorage", () => {
setStoredAuth({ username: "operator", password: "secret" });
expect(getStoredAuth()).toEqual({ username: "operator", password: "secret" });
expect(isAuthenticated()).toBe(true);
});
it("returns null for missing or invalid auth", () => {
expect(getStoredAuth()).toBeNull();
expect(isAuthenticated()).toBe(false);
sessionStorage.setItem("aetherforge_auth", "not-json");
expect(getStoredAuth()).toBeNull();
});
it("clears stored auth on logout", () => {
setStoredAuth({ username: "operator", password: "secret" });
clearStoredAuth();
expect(isAuthenticated()).toBe(false);
});
it("builds Basic auth header from stored credentials", () => {
setStoredAuth({ username: "operator", password: "secret" });
const header = getBasicAuthHeader();
expect(header).toBe(`Basic ${btoa("operator:secret")}`);
});
it("returns null auth header when unauthenticated", () => {
expect(getBasicAuthHeader()).toBeNull();
});
});

66
web/src/lib/auth.ts Normal file
View File

@@ -0,0 +1,66 @@
const AUTH_KEY = "aetherforge_auth";
export interface AuthCredentials {
username: string;
password: string;
}
export function getStoredAuth(): AuthCredentials | null {
const raw = sessionStorage.getItem(AUTH_KEY);
if (!raw) return null;
try {
const parsed = JSON.parse(raw) as AuthCredentials;
if (parsed.username && parsed.password) return parsed;
} catch {
sessionStorage.removeItem(AUTH_KEY);
}
return null;
}
export function setStoredAuth(creds: AuthCredentials): void {
sessionStorage.setItem(AUTH_KEY, JSON.stringify(creds));
}
export function clearStoredAuth(): void {
sessionStorage.removeItem(AUTH_KEY);
}
export function isAuthenticated(): boolean {
return getStoredAuth() !== null;
}
export function getBasicAuthHeader(): string | null {
const creds = getStoredAuth();
if (!creds) return null;
const encoded = btoa(`${creds.username}:${creds.password}`);
return `Basic ${encoded}`;
}
export async function authFetch(
input: RequestInfo | URL,
init: RequestInit = {},
): Promise<Response> {
const auth = getBasicAuthHeader();
const headers = new Headers(init.headers);
if (auth) headers.set("Authorization", auth);
return fetch(input, { ...init, headers });
}
export async function fetchWsTicket(): Promise<string | null> {
try {
const res = await authFetch("/api/v1/ws/ticket", { method: "POST" });
if (!res.ok) return null;
const data = (await res.json()) as { ticket?: string };
return data.ticket ?? null;
} catch {
return null;
}
}
export function buildWsUrl(ticket: string | null): string {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const host = window.location.host;
const base = `${protocol}//${host}/api/v1/ws/fleet`;
if (ticket) return `${base}?ticket=${encodeURIComponent(ticket)}`;
return base;
}

34
web/src/lib/utils.test.ts Normal file
View File

@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import { formatHashrate, formatUptime } from "./utils";
describe("formatHashrate", () => {
it("formats H/s for small values", () => {
expect(formatHashrate(500)).toBe("500 H/s");
});
it("formats KH/s", () => {
expect(formatHashrate(12_500)).toBe("12.50 KH/s");
});
it("formats MH/s", () => {
expect(formatHashrate(18_200_000)).toBe("18.20 MH/s");
});
it("formats GH/s", () => {
expect(formatHashrate(2_500_000_000)).toBe("2.50 GH/s");
});
it("formats TH/s", () => {
expect(formatHashrate(1_200_000_000_000)).toBe("1.20 TH/s");
});
});
describe("formatUptime", () => {
it("formats minutes only", () => {
expect(formatUptime(900)).toBe("15m");
});
it("formats hours and minutes", () => {
expect(formatUptime(86400)).toBe("24h 0m");
});
});

21
web/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,21 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function formatHashrate(hs: number): string {
if (hs >= 1e12) return `${(hs / 1e12).toFixed(2)} TH/s`;
if (hs >= 1e9) return `${(hs / 1e9).toFixed(2)} GH/s`;
if (hs >= 1e6) return `${(hs / 1e6).toFixed(2)} MH/s`;
if (hs >= 1e3) return `${(hs / 1e3).toFixed(2)} KH/s`;
return `${hs.toFixed(0)} H/s`;
}
export function formatUptime(seconds: number): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
if (h > 0) return `${h}h ${m}m`;
return `${m}m`;
}