Implement monitoring infrastructure
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
import Mining from "./pages/mining/Mining.svelte";
|
||||
import Footer from "./Footer.svelte";
|
||||
import BaseLayout from "./BaseLayout.svelte";
|
||||
import Monitoring from "./pages/monitoring/Monitoring.svelte";
|
||||
import Bar from "./pages/playground/Bar.svelte";
|
||||
|
||||
onMount(async () => {
|
||||
const res = await fetch("/user/me");
|
||||
@@ -25,7 +27,7 @@
|
||||
</BaseLayout>
|
||||
</Route>
|
||||
|
||||
<Route path="/foo">
|
||||
<Route path="foo">
|
||||
<BaseLayout>
|
||||
<section class="hero is-danger is-fullheight">
|
||||
<div class="hero-body">
|
||||
@@ -38,19 +40,19 @@
|
||||
</BaseLayout>
|
||||
</Route>
|
||||
|
||||
<Route path="/bar">
|
||||
<BaseLayout>bar</BaseLayout>
|
||||
<Route path="bar">
|
||||
<BaseLayout><Bar /></BaseLayout>
|
||||
</Route>
|
||||
|
||||
<Route path="/monitoring">
|
||||
<BaseLayout>monitoring</BaseLayout>
|
||||
<Route path="monitoring/*">
|
||||
<BaseLayout><Monitoring /></BaseLayout>
|
||||
</Route>
|
||||
|
||||
<Route path="/mining">
|
||||
<Route path="mining">
|
||||
<Mining />
|
||||
</Route>
|
||||
|
||||
<Route path="/stats">
|
||||
<Route path="stats">
|
||||
<BaseLayout>stats</BaseLayout>
|
||||
</Route>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<Link
|
||||
to={target}
|
||||
class="navbar-item is-tab {$location.pathname === target
|
||||
class="navbar-item is-tab {$location.pathname.startsWith(target)
|
||||
? 'is-active'
|
||||
: ''}"
|
||||
>
|
||||
|
||||
@@ -18,6 +18,11 @@ $fa-font-path: "@fortawesome/fontawesome-free/webfonts";
|
||||
@import "@fortawesome/fontawesome-free/scss/v4-shims.scss";
|
||||
|
||||
// https://github.com/mefechoel/svelte-navigator#what-are-the-weird-rectangles-around-the-headings-in-my-app
|
||||
h1:focus {
|
||||
h1:focus,
|
||||
h2:focus,
|
||||
h3:focus,
|
||||
h4:focus,
|
||||
h5:focus,
|
||||
h6:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
27
frontend/src/pages/monitoring/Monitoring.svelte
Normal file
27
frontend/src/pages/monitoring/Monitoring.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Link, Route, useParams } from "svelte-navigator";
|
||||
import Viewer from "./Viewer.svelte";
|
||||
|
||||
const uuids = ["8b9faf9f-9470-4a50-b405-0af5f0152550"];
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<Route path="/">
|
||||
<div class="content">
|
||||
<p>Yo wassup</p>
|
||||
<ul>
|
||||
{#each uuids as id}
|
||||
<li><Link to="./{id}">{id}</Link></li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</Route>
|
||||
|
||||
<Route path=":id" let:params>
|
||||
<div class="container">
|
||||
<h1 class="title">ASSUMING DIRECT CONTROL</h1>
|
||||
<Viewer uuid={params.id} />
|
||||
<Link to="..">fuck go back</Link>
|
||||
</div>
|
||||
</Route>
|
||||
</section>
|
||||
121
frontend/src/pages/monitoring/Screen.svelte
Normal file
121
frontend/src/pages/monitoring/Screen.svelte
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts">
|
||||
import termFont from "./term_font.png";
|
||||
import { loadFont, charWidth, charHeight } from "./font";
|
||||
import type { ScreenContent } from "./proto";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
|
||||
export let content: ScreenContent;
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let composeCanvas: HTMLCanvasElement;
|
||||
let font: ImageBitmap[];
|
||||
|
||||
const paddingX = 2,
|
||||
paddingY = 2;
|
||||
|
||||
const toColor = (n: number) => `#${n.toString(16).padStart(6, "0")}`;
|
||||
|
||||
// SAFETY: Only called if all of (content, canvas, composeCanvas, font) are defined
|
||||
function drawChar(
|
||||
charCode: number,
|
||||
x: number,
|
||||
y: number,
|
||||
textColor: number,
|
||||
backgroundColor: number
|
||||
) {
|
||||
let cx = canvas.getContext("2d");
|
||||
let ccx = composeCanvas.getContext("2d");
|
||||
|
||||
const offsetX = paddingX + x * charWidth;
|
||||
const offsetY = paddingY + y * charHeight;
|
||||
|
||||
let x0 = offsetX;
|
||||
let y0 = offsetY;
|
||||
let w = charWidth;
|
||||
let h = charHeight;
|
||||
|
||||
// handle padding
|
||||
if (x === 0) {
|
||||
x0 -= paddingX;
|
||||
w += paddingX;
|
||||
} else if (x === content.width - 1) {
|
||||
w += paddingX;
|
||||
}
|
||||
if (y === 0) {
|
||||
y0 -= paddingY;
|
||||
h += paddingY;
|
||||
} else if (y === content.height - 1) {
|
||||
h += paddingY;
|
||||
}
|
||||
|
||||
// draw background
|
||||
cx.fillStyle = toColor(content.palette[backgroundColor]);
|
||||
cx.fillRect(x0, y0, w, h);
|
||||
|
||||
// compose foreground
|
||||
ccx.globalCompositeOperation = "source-over";
|
||||
ccx.clearRect(0, 0, charWidth, charHeight);
|
||||
|
||||
ccx.fillStyle = toColor(content.palette[textColor]);
|
||||
ccx.fillRect(0, 0, charWidth, charHeight);
|
||||
ccx.globalCompositeOperation = "destination-in";
|
||||
ccx.drawImage(font[charCode], 0, 0);
|
||||
|
||||
// draw foreground
|
||||
cx.drawImage(composeCanvas, offsetX, offsetY);
|
||||
}
|
||||
|
||||
function redraw(content: ScreenContent) {
|
||||
if (!canvas) return;
|
||||
if (!font) return;
|
||||
|
||||
const cx = canvas.getContext("2d");
|
||||
|
||||
if (!content) {
|
||||
cx.fillStyle = "#ff6600";
|
||||
cx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.width = content.width * charWidth + 2 * paddingX;
|
||||
canvas.height = content.height * charHeight + 2 * paddingY;
|
||||
|
||||
cx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (let y = 0; y < content.height; ++y) {
|
||||
const line = content.text[y];
|
||||
const fgLine = content.fg_color[y];
|
||||
const bgLine = content.bg_color[y];
|
||||
|
||||
for (let x = 0; x < content.width; ++x) {
|
||||
drawChar(
|
||||
line.charCodeAt(x),
|
||||
x,
|
||||
y,
|
||||
parseInt(fgLine.charAt(x), 16),
|
||||
parseInt(bgLine.charAt(x), 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: redraw(content);
|
||||
|
||||
const blinkInt = setInterval(() => {
|
||||
|
||||
}, 400);
|
||||
|
||||
onMount(async () => {
|
||||
font = await loadFont(termFont);
|
||||
composeCanvas = document.createElement("canvas");
|
||||
composeCanvas.width = charWidth;
|
||||
composeCanvas.height = charHeight;
|
||||
redraw(content);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
composeCanvas = null;
|
||||
clearInterval(blinkInt);
|
||||
});
|
||||
</script>
|
||||
|
||||
<canvas bind:this={canvas} />
|
||||
65
frontend/src/pages/monitoring/Viewer.svelte
Normal file
65
frontend/src/pages/monitoring/Viewer.svelte
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
export let uuid: string;
|
||||
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import Screen from "./Screen.svelte";
|
||||
|
||||
const wsUrl = new URL(`/ipmi/browser/${uuid}/ws`, window.location.href);
|
||||
wsUrl.protocol = wsUrl.protocol.replace("http", "ws");
|
||||
|
||||
const dummyData = {
|
||||
x: -1,
|
||||
y: -1,
|
||||
width: 13,
|
||||
height: 5,
|
||||
blink: false,
|
||||
fg: 0,
|
||||
text: [
|
||||
" ",
|
||||
" ",
|
||||
" [NO SIGNAL] ",
|
||||
" ",
|
||||
" ",
|
||||
],
|
||||
fg_color: [
|
||||
"0000000000000",
|
||||
"0000000000000",
|
||||
"0800000000080",
|
||||
"0000000000000",
|
||||
"0000000000000",
|
||||
],
|
||||
bg_color: [
|
||||
"7777777777777",
|
||||
"7777777777777",
|
||||
"7777777777777",
|
||||
"7777777777777",
|
||||
"7777777777777",
|
||||
],
|
||||
palette: [
|
||||
15790320, 15905331, 15040472, 10072818, 14605932, 8375321, 15905484,
|
||||
5000268, 10066329, 5020082, 11691749, 3368652, 8349260, 5744206,
|
||||
13388876, 1118481,
|
||||
],
|
||||
};
|
||||
|
||||
let socket: WebSocket | null = null;
|
||||
let data;
|
||||
|
||||
onMount(async () => {
|
||||
socket = new WebSocket(wsUrl);
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
data = JSON.parse(event.data);
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
console.log("onDestroy");
|
||||
if (socket !== null) {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Screen content={data || dummyData} />
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
25
frontend/src/pages/monitoring/font.ts
Normal file
25
frontend/src/pages/monitoring/font.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export const charWidth = 6, charHeight = 9;
|
||||
|
||||
export async function loadFont(src: RequestInfo | URL): Promise<ImageBitmap[]> {
|
||||
const fontImg = await fetch(src);
|
||||
const fontBlob = await fontImg.blob();
|
||||
|
||||
async function getCharBitmap(x: number, y: number) {
|
||||
const fontOffsetX = 1, fontOffsetY = 1, fontPaddingX = 2, fontPaddingY = 2;
|
||||
|
||||
const offsetX = (charWidth + fontPaddingX) * x + fontOffsetX;
|
||||
const offsetY = (charHeight + fontPaddingY) * y + fontOffsetY;
|
||||
|
||||
return await createImageBitmap(fontBlob, offsetX, offsetY, charWidth, charHeight);
|
||||
}
|
||||
|
||||
const font = Array(256);
|
||||
|
||||
for (let y = 0; y < 16; ++y) {
|
||||
for (let x = 0; x < 16; ++x) {
|
||||
const i = 16 * y + x;
|
||||
font[i] = getCharBitmap(x, y);
|
||||
}
|
||||
}
|
||||
return await Promise.all(font);
|
||||
}
|
||||
12
frontend/src/pages/monitoring/proto.ts
Normal file
12
frontend/src/pages/monitoring/proto.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export type ScreenContent = {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
blink: boolean
|
||||
fg: number
|
||||
text: string[]
|
||||
fg_color: string[]
|
||||
bg_color: string[]
|
||||
palette: number[]
|
||||
}
|
||||
BIN
frontend/src/pages/monitoring/term_font.png
Normal file
BIN
frontend/src/pages/monitoring/term_font.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
21
frontend/src/pages/playground/Bar.svelte
Normal file
21
frontend/src/pages/playground/Bar.svelte
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from "svelte";
|
||||
import Canvas from "./Canvas.svelte";
|
||||
|
||||
let colors = ["#ff6600", "#00ff66", "#6600ff"];
|
||||
|
||||
let i = 0;
|
||||
$: color = colors[i];
|
||||
|
||||
let int = setInterval(() => {
|
||||
i = (i + 1) % colors.length;
|
||||
}, 1000);
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(int);
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="section">
|
||||
<Canvas {color} />
|
||||
</section>
|
||||
24
frontend/src/pages/playground/Canvas.svelte
Normal file
24
frontend/src/pages/playground/Canvas.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
export let color: string;
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
|
||||
let redraws = 0;
|
||||
|
||||
function redraw(color) {
|
||||
if (!canvas) return;
|
||||
|
||||
redraws += 1;
|
||||
let cx = canvas.getContext("2d");
|
||||
cx.fillStyle = color;
|
||||
cx.fillRect(0, 0, cx.canvas.width, cx.canvas.height);
|
||||
}
|
||||
|
||||
$: redraw(color);
|
||||
</script>
|
||||
|
||||
<canvas bind:this={canvas} />
|
||||
<pre>color: {color}
|
||||
redraws: {redraws}</pre>
|
||||
Reference in New Issue
Block a user