Skip to content

Commit

Permalink
Merge pull request #92 from unyt-org/feat-debug-pages
Browse files Browse the repository at this point in the history
Add web debug interface pages (network and logs)
  • Loading branch information
jonasstrehle authored Feb 10, 2024
2 parents 633abe3 + aa1d07a commit dc5c7ec
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 62 deletions.
122 changes: 69 additions & 53 deletions deno.lock

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions src/app/debugging/logs-backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Datex } from "datex-core-legacy/mod.ts";
import { LOG_LEVEL } from "datex-core-legacy/utils/logger.ts";

@endpoint export class DebugLogs {
@property static getLogs() {

const timeout = 60; // 60 minutes

const stream = $$(new Datex.Stream<string|ArrayBuffer>());
Datex.Logger.logToStream(stream);

// close stream after timeout
setTimeout(() => {
// \u0004 is the EOT character
stream.write(new TextEncoder().encode("\n[Stream was closed after " + timeout + " minutes]\n\u0004").buffer);
stream.close();
}, timeout * 60 * 1000);

return stream;
}

@property static enableMessageLogger() {
enableMessageLogger()
}

@property static disableMessageLogger() {
disableMessageLogger()
}

@property static enableVerboseLogs() {
Datex.Logger.production_log_level = LOG_LEVEL.VERBOSE
Datex.Logger.development_log_level = LOG_LEVEL.VERBOSE
}

@property static disableVerboseLogs() {
Datex.Logger.production_log_level = LOG_LEVEL.DEFAULT
Datex.Logger.development_log_level = LOG_LEVEL.DEFAULT
}
}
62 changes: 62 additions & 0 deletions src/app/debugging/logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { DebugLogs } from "./logs-backend.ts";
import { app } from "../app.ts"
import { convertANSIToHTML } from "../../utils/ansi-to-html.ts";
import { unsafeHTML } from "../../html/unsafe-html.ts";

const messageLoggerEnabled = $$(false);
const verboseLogsEnabled = $$(false);

const logsContainer = <div style="width:100%;overflow:scroll;"></div>
const component = <div style="height:100vh;overflow: hidden;padding:20px;display: flex;flex-direction: column;">
<div style="user-select: none;margin-bottom:10px;padding:10px;background:var(--bg-content);border-radius:10px;display:flex;gap:10px;align-items: center;">
<label style="display:flex;align-items: center;">
<input type="checkbox" checked={messageLoggerEnabled} style="margin: 0 5px;"/>
Enable Message Logger
</label>
<label style="display:flex;align-items: center;">
<input type="checkbox" checked={verboseLogsEnabled} style="margin: 0 5px;"/>
Enable Verbose Logs
</label>
<button style="margin-left: auto;border: none;margin-bottom: 0;background:var(--red)" onclick={()=>logsContainer.innerHTML=""}>Clear logs</button>
<button style="border: none;margin-bottom: 0;" onclick={()=>logsContainer.scrollTo(0, logsContainer.scrollHeight)}>Scroll to bottom</button>
</div>
<b style="margin-bottom:20px;">
Backend Logs ({app.backend?.toString()})
</b>
{logsContainer}
</div>;

effect(() => {
if (messageLoggerEnabled.val) DebugLogs.enableMessageLogger.to(app.backend!)();
else DebugLogs.disableMessageLogger.to(app.backend!)();
});

effect(() => {
if (verboseLogsEnabled.val) DebugLogs.enableVerboseLogs.to(app.backend!)();
else DebugLogs.disableVerboseLogs.to(app.backend!)();
});


(document as any).body.appendChild(component);
(document as any).body.style.margin = "0";

const stream = await DebugLogs.getLogs.to(app.backend!)();

const reader = stream.getReader();

while (true) {
const val = await reader.read();
const child = <span style="display:flex;gap:20px;">
<span style="color:var(--text-light);white-space: nowrap;">{new Date().toLocaleTimeString()}</span>
<span>{unsafeHTML(convertANSIToHTML(val.value as string))}</span>
</span>;
const scrollDown = Math.abs(logsContainer.scrollHeight - logsContainer.scrollTop - logsContainer.clientHeight) < 1;
logsContainer.appendChild(child);
setTimeout(() => {
if (scrollDown) {
logsContainer.scrollTo(0, logsContainer.scrollHeight);
}
}, 10);
if (val.done) break;
}

9 changes: 9 additions & 0 deletions src/app/debugging/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(document as any).body.append(
<div style="padding: 15px;">
<h1>UIX Debugging Tools</h1>
<ul>
<li><a href="/@debug/logs">Backend Logs</a></li>
<li><a href="/@debug/network">Network Status</a></li>
</ul>
</div>
);
11 changes: 11 additions & 0 deletions src/app/debugging/network-backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {communicationHub} from "datex-core-legacy/network/communication-hub.ts";

@endpoint export class DebugNetwork {
@property static getComStatus() {
return communicationHub.handler.getStatus()
}

@property static getEndpointSockets(endpoint: string) {
return communicationHub.handler.getEndpointSockets(endpoint)
}
}
36 changes: 36 additions & 0 deletions src/app/debugging/network.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { DebugNetwork } from "./network-backend.ts";
import { convertANSIToHTML } from "../../utils/ansi-to-html.ts";
import { app } from "../app.ts";

const endpoint = $$("")

const status = <div style="background: var(--accent-bg);border-radius: 10px;padding: 15px;"></div>;
const endpointSockets = <div></div>
const container = <div>
{status}
<div style="display:flex;gap:10px;margin:20px 0px;">
<input type="text" value={endpoint} style="width:500px"/>
<input type="button" value="Get Endpoint Sockets" onclick={updateEndpointSockets}/>
</div>
{endpointSockets}
</div>


const getComStatus = DebugNetwork.getComStatus.to(app.backend!)
const getEndpointSockets = DebugNetwork.getEndpointSockets.to(app.backend!)

async function updateContent() {
const content = await getComStatus();
status.innerHTML = convertANSIToHTML(content)
}

async function updateEndpointSockets() {
const content = await getEndpointSockets(endpoint.val);
endpointSockets.innerHTML = convertANSIToHTML(content)
}


updateContent();
setInterval(updateContent, 3000);

(document as any).body.append(container);
31 changes: 24 additions & 7 deletions src/app/frontend-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ export class FrontendManager extends HTMLProvider {
}

#BLANK_PAGE_URL = 'uix/base/blank.ts';
#LOGS_PAGE_URL = 'uix/app/debugging/logs.tsx';
#NETWORK_PAGE_URL = 'uix/app/debugging/network.tsx';
#DEBUG_LANDING_PAGE_URL = 'uix/app/debugging/main.tsx';

server!: Server
transpiler!: Transpiler
Expand Down Expand Up @@ -289,7 +292,7 @@ export class FrontendManager extends HTMLProvider {
}
});

this.server.path("/@uix/window", (req, path)=>this.handleNewHTML(req, path));
this.server.path("/@uix/window", (req, path)=>this.serveUIXPage(req, this.#BLANK_PAGE_URL));
if (this.app_options.installable || this.app_options.manifest) this.server.path("/@uix/manifest.json", (req, path)=>this.handleManifest(req, path));
this.server.path("/@uix/sw.js", (req, path)=>this.handleServiceWorker(req, path));
this.server.path("/@uix/sw.ts", (req, path)=>this.handleServiceWorker(req, path));
Expand Down Expand Up @@ -373,7 +376,20 @@ export class FrontendManager extends HTMLProvider {

});


// debug mode enabled
if (this.app_options.debug_mode) {
await import("./debugging/logs-backend.ts")
await import("./debugging/network-backend.ts")
this.server.path("/@debug/logs", (req) => this.serveUIXPage(req, this.#LOGS_PAGE_URL));
this.server.path("/@debug/network", (req) => this.serveUIXPage(req, this.#NETWORK_PAGE_URL));
this.server.path(/^\/@debug($|\/.*)/, (req) => this.serveUIXPage(req, this.#DEBUG_LANDING_PAGE_URL));
}

else {
this.server.path(/^\/@debug($|\/.*)/, (req) => req.respondWith(this.server.getErrorResponse(500, "Debug mode not enabled")))
}


// handle datex-over-http
this.server.path(/^\/@uix\/datex\/?$/, async (req, path)=>{
try {
Expand All @@ -394,7 +410,7 @@ export class FrontendManager extends HTMLProvider {
}
catch (e) {
console.log(e)
req.respondWith(await this.server.getErrorResponse(500, e?.message ?? e?.toString()));
req.respondWith(this.server.getErrorResponse(500, e?.message ?? e?.toString()));
}
});

Expand Down Expand Up @@ -978,16 +994,17 @@ if (!window.location.origin.endsWith(".unyt.app")) {
}
}

// html page for new empty pages (includes blank.ts)
private async handleNewHTML(requestEvent: Deno.RequestEvent, _path:string) {
private async serveUIXPage(requestEvent: Deno.RequestEvent, scriptPath: string) {
await this.server.serveContent(requestEvent, "text/html", await generateHTMLPage({
provider: this,
prerendered_content: "",
render_method: RenderMethod.DYNAMIC,
js_files: [...this.#client_scripts, this.#BLANK_PAGE_URL],
js_files: [...this.#client_scripts, scriptPath],
static_js_files: this.#static_client_scripts,
global_css_files: ['uix/style/document.css'],
body_css_files: ['uix/style/body.css']
body_css_files: ['uix/style/body.css'],
color_scheme: "dark",
force_enable_scripts: true
}));
}

Expand Down
2 changes: 2 additions & 0 deletions src/app/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type appOptions = {
import_map?: {imports:Record<string,string>} // prefer over import map path

experimentalFeatures?: string|string[]
debug_mode?: boolean // enable debug interfaces available on /@debug/...
}

export interface normalizedAppOptions extends appOptions {
Expand Down Expand Up @@ -61,6 +62,7 @@ export async function normalizeAppOptions(options:appOptions = {}, base_url?:str
n_options.meta = options.meta;

n_options.experimentalFeatures = options.experimentalFeatures ? (options.experimentalFeatures instanceof Array ? options.experimentalFeatures : [options.experimentalFeatures]) : [];
n_options.debug_mode = options.debug_mode ?? false;

// import map or import map path
if (options.import_map) n_options.import_map = new ImportMap(options.import_map);
Expand Down
6 changes: 4 additions & 2 deletions src/html/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,8 @@ export type HTMLPageOptions = {
open_graph_meta_tags?:OpenGraphInformation,
lang?: string,
livePointers?: string[],
includeImportMap?: boolean
includeImportMap?: boolean,
force_enable_scripts?: boolean
}


Expand All @@ -633,6 +634,7 @@ export async function generateHTMLPage({
body_css_files,
frontend_entrypoint,
backend_entrypoint,
force_enable_scripts,
open_graph_meta_tags,
lang,
livePointers,
Expand All @@ -651,7 +653,7 @@ export async function generateHTMLPage({
let metaScripts = ''

// use frontendRuntime if rendering DYNAMIC or HYDRATION, and entrypoints are loaded, otherwise just static content and standalone js
const useFrontendRuntime = (render_method == RenderMethod.DYNAMIC || render_method == RenderMethod.HYBRID || render_method == RenderMethod.PREVIEW) && !!(frontend_entrypoint || backend_entrypoint || provider.live);
const useFrontendRuntime = (render_method == RenderMethod.DYNAMIC || render_method == RenderMethod.HYBRID || render_method == RenderMethod.PREVIEW) && !!(frontend_entrypoint || backend_entrypoint || provider.live || force_enable_scripts);

// js files
if (useFrontendRuntime) {
Expand Down

0 comments on commit dc5c7ec

Please sign in to comment.