add stats to frontend

This commit is contained in:
marlonerler
2024-07-05 16:49:08 +02:00
parent dd5d1b7e26
commit 65cd27c6f5
7 changed files with 97 additions and 7 deletions
+4 -2
View File
@@ -1,11 +1,11 @@
import { ServerWebSocket, write } from "bun"; import { ServerWebSocket, write } from "bun";
import { import {
addConnection, addConnection,
audit,
connectServer, connectServer,
connectServers, connectServers,
connectedClientCount, connectedClientCount,
forgetConnection, forgetConnection,
getStats,
processMessage, processMessage,
subscribeChannel, subscribeChannel,
subscriptions, subscriptions,
@@ -42,6 +42,8 @@ async function main() {
switch (requestPath) { switch (requestPath) {
case "/": case "/":
return createFileResponse(defaultPage); return createFileResponse(defaultPage);
case "/stats":
return new Response(JSON.stringify(getStats()));
default: default:
return createFileResponse(requestPath); return createFileResponse(requestPath);
} }
@@ -83,7 +85,7 @@ async function main() {
writeInfo("server ip address", Colors.bold.green(IP.address())); writeInfo("server ip address", Colors.bold.green(IP.address()));
console.log(); console.log();
audit().forEach((entry) => writeStat(...entry)); getStats().forEach((entry) => writeStat(...entry));
} }
setInterval(updateCLI, 5000); setInterval(updateCLI, 5000);
+28 -2
View File
@@ -186,6 +186,7 @@
var staticTextEnglish = { var staticTextEnglish = {
channel: "Channel", channel: "Channel",
channel_placeholder: "my-channel", channel_placeholder: "my-channel",
info: "Info",
message: "Message", message: "Message",
messages: "Messages", messages: "Messages",
message_placeholder: "Hello, world!", message_placeholder: "Hello, world!",
@@ -194,6 +195,7 @@
messageLastReceived: "Last received message", messageLastReceived: "Last received message",
messagesReceived: "Received Messages", messagesReceived: "Received Messages",
send: "Send", send: "Send",
serverInfo: "Server Info",
settings: "Settings", settings: "Settings",
subscribeToChannel: "Subscribe to channel", subscribeToChannel: "Subscribe to channel",
subscribe: "Subscribe", subscribe: "Subscribe",
@@ -204,6 +206,7 @@
es: { es: {
channel: "Canal", channel: "Canal",
channel_placeholder: "mi-canal", channel_placeholder: "mi-canal",
info: "Info",
message: "Mensaje", message: "Mensaje",
messages: "Mensajes", messages: "Mensajes",
message_placeholder: "Hola!", message_placeholder: "Hola!",
@@ -212,6 +215,7 @@
messageLastReceived: "\xDCltimo mensaje", messageLastReceived: "\xDCltimo mensaje",
messagesReceived: "Todos los Mensajes", messagesReceived: "Todos los Mensajes",
send: "Enviar", send: "Enviar",
serverInfo: "Informaci\xF3n",
settings: "Configuraci\xF3n", settings: "Configuraci\xF3n",
subscribeToChannel: "Suscribirse a un canal", subscribeToChannel: "Suscribirse a un canal",
subscribe: "Suscribirse", subscribe: "Suscribirse",
@@ -220,6 +224,7 @@
de: { de: {
channel: "Kanal", channel: "Kanal",
channel_placeholder: "mein-kanal", channel_placeholder: "mein-kanal",
info: "Daten",
message: "Nachricht", message: "Nachricht",
messages: "Nachrichten", messages: "Nachrichten",
message_placeholder: "Hallo!", message_placeholder: "Hallo!",
@@ -228,6 +233,7 @@
messageLastReceived: "Letzte Nachrichte", messageLastReceived: "Letzte Nachrichte",
messagesReceived: "Empfangene Nachrichten", messagesReceived: "Empfangene Nachrichten",
send: "Senden", send: "Senden",
serverInfo: "Serverdaten",
settings: "Einstellungen", settings: "Einstellungen",
subscribeToChannel: "Kanal Abonnieren", subscribeToChannel: "Kanal Abonnieren",
subscribe: "Abonnieren", subscribe: "Abonnieren",
@@ -250,6 +256,7 @@
uuid = new UUID(); uuid = new UUID();
}; };
var ws = new WebSocket(`ws://${window.location.host}`); var ws = new WebSocket(`ws://${window.location.host}`);
ws.addEventListener("open", () => updateStats());
ws.addEventListener("message", (message) => { ws.addEventListener("message", (message) => {
const formatted = formatMessage(message); const formatted = formatMessage(message);
const { messageChannel, messageBody } = parseMessage(message); const { messageChannel, messageBody } = parseMessage(message);
@@ -265,6 +272,19 @@
var lastReceivedMessage = new State( var lastReceivedMessage = new State(
getText("noMessagesReceived") getText("noMessagesReceived")
); );
var statHTMLString = new State("");
async function updateStats() {
const response = await fetch(
`${window.location.protocol}//${window.location.host}/stats`
);
const data = await response.json();
let html = "";
data.forEach((item) => {
const tile = /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "secondary flex width-100" }, item[0].toString()), /* @__PURE__ */ createElement("b", null, item[1].toString()));
html += tile.outerHTML;
});
statHTMLString.value = html;
}
var subscriptionChannel = new State(""); var subscriptionChannel = new State("");
var newMessageChannel = new State(""); var newMessageChannel = new State("");
var newMessageBody = new State(""); var newMessageBody = new State("");
@@ -293,6 +313,7 @@
sendToWS({ unsubscribeChannel: subscriptionChannel.value }); sendToWS({ unsubscribeChannel: subscriptionChannel.value });
} }
function sendMessage() { function sendMessage() {
if (isMessageEmpty.value == true) return;
sendToWS({ sendToWS({
messageChannel: newMessageChannel.value, messageChannel: newMessageChannel.value,
messageBody: newMessageBody.value messageBody: newMessageBody.value
@@ -300,6 +321,11 @@
newMessageBody.value = ""; newMessageBody.value = "";
} }
// src/infoScreen.tsx
function InfoScreen() {
return /* @__PURE__ */ createElement("article", { id: "info-screen" }, /* @__PURE__ */ createElement("header", null, getText("serverInfo"), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement("button", { "on:click": updateStats }, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh")))), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "flex-column gap", "subscribe:innerHTML": statHTMLString })));
}
// src/mainScreen.tsx // src/mainScreen.tsx
function MainScreen() { function MainScreen() {
return /* @__PURE__ */ createElement("article", { id: "main-screen" }, /* @__PURE__ */ createElement("header", null, getText("settings")), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "tile width-input" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, getText("messageLastReceived")), /* @__PURE__ */ createElement("p", { class: "secondary", "subscribe:innerText": lastReceivedMessage }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, getText("subscribeToChannel")), /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("article", { id: "main-screen" }, /* @__PURE__ */ createElement("header", null, getText("settings")), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "tile width-input" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, getText("messageLastReceived")), /* @__PURE__ */ createElement("p", { class: "secondary", "subscribe:innerText": lastReceivedMessage }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, getText("subscribeToChannel")), /* @__PURE__ */ createElement(
@@ -367,7 +393,7 @@
// src/index.tsx // src/index.tsx
document.body.prepend( document.body.prepend(
/* @__PURE__ */ createElement("menu", null, /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#main-screen", active: true }, /* @__PURE__ */ createElement("span", { class: "icon" }, "settings"), getText("settings")), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#message-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "chat"), getText("messages"))) /* @__PURE__ */ createElement("menu", null, /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#info-screen", active: true }, /* @__PURE__ */ createElement("span", { class: "icon" }, "info"), getText("info")), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#main-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "settings"), getText("settings")), /* @__PURE__ */ createElement("a", { class: "tab-link", href: "#message-screen" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "chat"), getText("messages")))
); );
document.querySelector("main").append(MainScreen(), MessageScreen()); document.querySelector("main").append(InfoScreen(), MainScreen(), MessageScreen());
})(); })();
+9 -2
View File
@@ -1,5 +1,6 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import { InfoScreen } from "./infoScreen";
import { MainScreen } from "./mainScreen"; import { MainScreen } from "./mainScreen";
import { MessageScreen } from "./messageScreen"; import { MessageScreen } from "./messageScreen";
import { getText } from "./translations"; import { getText } from "./translations";
@@ -7,7 +8,11 @@ import { getText } from "./translations";
// WS // WS
document.body.prepend( document.body.prepend(
<menu> <menu>
<a class="tab-link" href="#main-screen" active> <a class="tab-link" href="#info-screen" active>
<span class="icon">info</span>
{getText("info")}
</a>
<a class="tab-link" href="#main-screen">
<span class="icon">settings</span> <span class="icon">settings</span>
{getText("settings")} {getText("settings")}
</a> </a>
@@ -17,4 +22,6 @@ document.body.prepend(
</a> </a>
</menu> </menu>
); );
document.querySelector("main")!.append(MainScreen(), MessageScreen()); document
.querySelector("main")!
.append(InfoScreen(), MainScreen(), MessageScreen());
+23
View File
@@ -0,0 +1,23 @@
import * as React from "bloatless-react";
import { statHTMLString, updateStats } from "./model";
import { getText } from "./translations";
export function InfoScreen() {
return (
<article id="info-screen">
<header>
{getText("serverInfo")}
<span>
<button on:click={updateStats}>
<span class="icon">refresh</span>
</button>
</span>
</header>
<div>
<div class="flex-column gap" subscribe:innerHTML={statHTMLString}></div>
</div>
</article>
);
}
+26
View File
@@ -11,6 +11,8 @@ export class Message implements React.Identifiable {
// WS // WS
const ws = new WebSocket(`ws://${window.location.host}`); const ws = new WebSocket(`ws://${window.location.host}`);
ws.addEventListener("open", () => updateStats());
ws.addEventListener("message", (message) => { ws.addEventListener("message", (message) => {
const formatted = formatMessage(message); const formatted = formatMessage(message);
const { messageChannel, messageBody } = parseMessage(message); const { messageChannel, messageBody } = parseMessage(message);
@@ -25,11 +27,34 @@ ws.addEventListener("message", (message) => {
}); });
// STATE // STATE
// messages
export const messages = new React.ListState<Message>(); export const messages = new React.ListState<Message>();
export const lastReceivedMessage = new React.State( export const lastReceivedMessage = new React.State(
getText("noMessagesReceived") getText("noMessagesReceived")
); );
// stats
export const statHTMLString = new React.State<string>("");
export async function updateStats() {
const response = await fetch(
`${window.location.protocol}//${window.location.host}/stats`
);
const data: [string, string][] = await response.json();
let html = "";
data.forEach((item) => {
const tile = (
<div class="tile">
<span class="secondary flex width-100">{item[0].toString()}</span>
<b>{item[1].toString()}</b>
</div>
);
html += tile.outerHTML;
});
statHTMLString.value = html;
}
// elements
export const subscriptionChannel = new React.State(""); export const subscriptionChannel = new React.State("");
export const newMessageChannel = new React.State(""); export const newMessageChannel = new React.State("");
@@ -67,6 +92,7 @@ export function unsubscribe() {
} }
export function sendMessage() { export function sendMessage() {
if (isMessageEmpty.value == true) return;
sendToWS({ sendToWS({
messageChannel: newMessageChannel.value, messageChannel: newMessageChannel.value,
messageBody: newMessageBody.value, messageBody: newMessageBody.value,
+6
View File
@@ -2,6 +2,7 @@
export const staticTextEnglish = { export const staticTextEnglish = {
channel: "Channel", channel: "Channel",
channel_placeholder: "my-channel", channel_placeholder: "my-channel",
info: "Info",
message: "Message", message: "Message",
messages: "Messages", messages: "Messages",
message_placeholder: "Hello, world!", message_placeholder: "Hello, world!",
@@ -10,6 +11,7 @@ export const staticTextEnglish = {
messageLastReceived: "Last received message", messageLastReceived: "Last received message",
messagesReceived: "Received Messages", messagesReceived: "Received Messages",
send: "Send", send: "Send",
serverInfo: "Server Info",
settings: "Settings", settings: "Settings",
subscribeToChannel: "Subscribe to channel", subscribeToChannel: "Subscribe to channel",
subscribe: "Subscribe", subscribe: "Subscribe",
@@ -21,6 +23,7 @@ export const translations: { [key: string]: typeof staticTextEnglish } = {
es: { es: {
channel: "Canal", channel: "Canal",
channel_placeholder: "mi-canal", channel_placeholder: "mi-canal",
info: "Info",
message: "Mensaje", message: "Mensaje",
messages: "Mensajes", messages: "Mensajes",
message_placeholder: "Hola!", message_placeholder: "Hola!",
@@ -29,6 +32,7 @@ export const translations: { [key: string]: typeof staticTextEnglish } = {
messageLastReceived: "Ültimo mensaje", messageLastReceived: "Ültimo mensaje",
messagesReceived: "Todos los Mensajes", messagesReceived: "Todos los Mensajes",
send: "Enviar", send: "Enviar",
serverInfo: "Información",
settings: "Configuración", settings: "Configuración",
subscribeToChannel: "Suscribirse a un canal", subscribeToChannel: "Suscribirse a un canal",
subscribe: "Suscribirse", subscribe: "Suscribirse",
@@ -37,6 +41,7 @@ export const translations: { [key: string]: typeof staticTextEnglish } = {
de: { de: {
channel: "Kanal", channel: "Kanal",
channel_placeholder: "mein-kanal", channel_placeholder: "mein-kanal",
info: "Daten",
message: "Nachricht", message: "Nachricht",
messages: "Nachrichten", messages: "Nachrichten",
message_placeholder: "Hallo!", message_placeholder: "Hallo!",
@@ -45,6 +50,7 @@ export const translations: { [key: string]: typeof staticTextEnglish } = {
messageLastReceived: "Letzte Nachrichte", messageLastReceived: "Letzte Nachrichte",
messagesReceived: "Empfangene Nachrichten", messagesReceived: "Empfangene Nachrichten",
send: "Senden", send: "Senden",
serverInfo: "Serverdaten",
settings: "Einstellungen", settings: "Einstellungen",
subscribeToChannel: "Kanal Abonnieren", subscribeToChannel: "Kanal Abonnieren",
subscribe: "Abonnieren", subscribe: "Abonnieren",
+1 -1
View File
@@ -109,7 +109,7 @@ export function subscribeChannel(server: WS, channel: string): void {
} }
// AUDIT // AUDIT
export function audit(): [string, number][] { export function getStats(): [string, number][] {
return [ return [
["channels", subscriptions.clientsPerChannel.size], ["channels", subscriptions.clientsPerChannel.size],
["clients connected", connectedClientCount], ["clients connected", connectedClientCount],