Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
43
web/src/lib/auth.test.ts
Normal file
43
web/src/lib/auth.test.ts
Normal 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
66
web/src/lib/auth.ts
Normal 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
34
web/src/lib/utils.test.ts
Normal 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
21
web/src/lib/utils.ts
Normal 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`;
|
||||
}
|
||||
Reference in New Issue
Block a user