From 3b0652890609dbd5f65b94789e8d80590524c22e Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 27 Nov 2018 13:50:56 +0200 Subject: [PATCH] WIP towards the tails GUI plugins * Tup automated builds with hot-code reloading * Most the Plugin API fleshed out. The work-in-progress P2P traffic visualition plugin is not committed yet. --- chronicles_tail/jsplugins.nim | 65 +++++++++++++++++++++++++++++++++-- webui/Tupfile | 1 + webui/karax_app.html | 5 ++- webui/karax_app.nim | 26 +++++++------- webui/server.nim | 23 +++++++++---- webui/styles.css | 15 ++++---- 6 files changed, 106 insertions(+), 29 deletions(-) diff --git a/chronicles_tail/jsplugins.nim b/chronicles_tail/jsplugins.nim index a285db4..7e66f2c 100644 --- a/chronicles_tail/jsplugins.nim +++ b/chronicles_tail/jsplugins.nim @@ -1,5 +1,8 @@ import - karax/vdom + jsffi, karax/[karax, vdom] + +export + karax, vdom type SectionRenderer* = proc(): VNode @@ -8,12 +11,68 @@ type title*: cstring content*: SectionRenderer + TailEvent* = object of js + msg*: cstring + level*: cstring + ts*: cstring + protocol*: cstring + msgId*: int + peer*: cstring + port*: int + data*: js + topics*: cstring + + TailEventFilter* = proc(e: TailEvent): bool + when defined(createChroniclesTail): + proc getKarax*: KaraxInstance {.exportc.} = kxi + var sections* = newSeq[Section](0) + filters* = newSeq[TailEventFilter]() proc addSection*(title: cstring, content: SectionRenderer) {.exportc.} = sections.add Section(title: title, content: content) -else: - proc addSection*(title: cstring, content: SectionRenderer) {.importc.} + redrawSync() + + proc addEventFilter*(f: TailEventFilter) {.exportc.} = + filters.add f + + proc addEscaped*(result: var string, s: string) {.exportc.} = + ## same as ``result.add(escape(s))``, but more efficient. + for c in items(s): + case c + of '<': result.add("<") + of '>': result.add(">") + of '&': result.add("&") + of '"': result.add(""") + of '\'': result.add("'") + of '/': result.add("/") + else: result.add(c) + + proc addAsHtml*(result: var string, obj: js) {.exportc.} = + if jsTypeOf(obj) == "object": + result.add "" + for key, value in obj: + result.add """" + result.add "
""" + result.addEscaped $key + result.add """""" + result.addAsHtml value + result.add "
" + else: + result.add cast[cstring](obj.toString()) + + proc asHtml*(obj: js): string {.exportc.} = + result = newStringOfCap(128) + result.addAsHtml(obj) + +else: + proc addEscaped*(result: var string, s: string) {.importc.} + proc addAsHtml*(result: var string, obj: js) {.importc.} + proc asHtml*(obj: js): string {.importc.} + + proc getKarax*(): KaraxInstance {.importc.} + proc addSection*(title: cstring, content: SectionRenderer) {.importc.} + proc addEventFilter*(f: TailEventFilter) {.importc.} diff --git a/webui/Tupfile b/webui/Tupfile index 547fd3e..4f2b9ce 100644 --- a/webui/Tupfile +++ b/webui/Tupfile @@ -1 +1,2 @@ : karax_app.nim |> nim --hotCodeReloading:on -d:createChroniclesTail -o:%o js %f |> %B.js +: eth_p2p_plugin.nim |> nim --hotCodeReloading:on -o:%o js %f |> %B.js diff --git a/webui/karax_app.html b/webui/karax_app.html index 7ac2c85..edbce6f 100644 --- a/webui/karax_app.html +++ b/webui/karax_app.html @@ -8,7 +8,10 @@ -
+
+
+
+ diff --git a/webui/karax_app.nim b/webui/karax_app.nim index b489139..c25d9f3 100644 --- a/webui/karax_app.nim +++ b/webui/karax_app.nim @@ -3,7 +3,7 @@ include import jsconsole, jscore, jsffi, jswebsockets, - tail_jsplugins + chronicles_tail/jsplugins type ChroniclesPrompt = object @@ -11,13 +11,11 @@ type suggestions: seq[string] activeSuggestionIdx: int - LogEvent = js - var chroniclesCredentials* {.importc, nodecl.}: js chroniclesPrompt: ChroniclesPrompt activeSectionIdx = 0 - logEvents = newSeq[LogEvent]() + logEvents = newSeq[TailEvent]() proc activeOrInactive(idx, currentActiveIdx: int): cstring = if idx == currentActiveIdx: "active" @@ -48,14 +46,12 @@ proc logSectionContent: VNode = tbody: for event in logEvents: - let level = cast[cstring](event.level) + let level = event.level tr(class = level): td(class = "level"): text level - td(class = "ts") : text cast[cstring](event.ts) - td(class = "msg") : text cast[cstring](event.msg) - td(class = "props"): text cast[cstring](event.x) - -addSection("Log", logSectionContent) + td(class = "ts") : text event.ts + td(class = "msg") : text event.msg + td(class = "props"): text event.data.asHtml var chroniclesSocket = newWebSocket(cast[cstring](chroniclesCredentials.url)) @@ -63,8 +59,13 @@ chroniclesSocket.onOpen = proc (e: jswebsockets.Event) = chroniclesSocket.send(cast[cstring](chroniclesCredentials.accessToken)) chroniclesSocket.onMessage = proc (e: MessageEvent) = - var msg = JSON.parse(e.data).js - if msg.level == jsundefined: + var msg = cast[TailEvent](JSON.parse(e.data)) + + for f in filters: + if f(msg): + return + + if msg.level.toJs == jsundefined: console.log msg return @@ -100,4 +101,5 @@ proc pageContent(): VNode = activeSection() setRenderer pageContent +addSection "Log", logSectionContent diff --git a/webui/server.nim b/webui/server.nim index 9716a56..5ca0a09 100644 --- a/webui/server.nim +++ b/webui/server.nim @@ -1,12 +1,19 @@ import - macros, json, random, times, strutils, + macros, json, random, times, strutils, os, asynchttpserver, asyncnet, asyncdispatch, websocket, chronicles_tail/configuration const indexFile = staticRead "karax_app.html" faviconBytes = staticRead "favicon.png" - styles = staticRead "styles.css" + +template fileContents(file: string): string = + when defined(debug): + readFile("webui" / file) + else: + const contents = staticRead(file) + contents + type WebuiServer* = ref object @@ -45,7 +52,7 @@ proc serve*(s: WebuiServer): Future[void] = var nextLineToSend = 0 while nextLineToSend < s.logLines.len: - await ws.sendText(s.logLines[nextLineToSend]) + await ws.sendText(s.logLines[nextLineToSend], maskingKey = "") inc nextLineToSend s.clients.add ws @@ -54,9 +61,13 @@ proc serve*(s: WebuiServer): Future[void] = await req.respond(Http200, indexFile) of "/styles.css": - await req.respond(Http200, styles, + await req.respond(Http200, fileContents("styles.css"), newHttpHeaders({"Content-Type": "text/css"})) + of "/eth_p2p_plugin.js": + await req.respond(Http200, readFile("webui/eth_p2p_plugin.js"), + newHttpHeaders({"Content-Type": "application/javascript"})) + of "/karax_app.js": var scripts = newStringOfCap(16000) @@ -69,8 +80,8 @@ proc serve*(s: WebuiServer): Future[void] = scripts.add compileJs("karax_app.nim", "-d:createChroniclesTail") - for plugin in s.plugins: - scripts.add plugin + # for plugin in s.plugins: + # scripts.add plugin await req.respond(Http200, scripts, newHttpHeaders({"Content-Type": "application/javascript"})) diff --git a/webui/styles.css b/webui/styles.css index 39c53b8..2d15238 100644 --- a/webui/styles.css +++ b/webui/styles.css @@ -71,15 +71,16 @@ table { flex-direction: row; align-items: center; color: white; - flex: 0; + flex: 0 auto; + font-size: 16px; } #header h1 { justify-self: flex-end; order: 3; text-align: right; - flex: 1; - font-size: 13px; + flex: 1 auto; + font-size: 15px; } #content, #header { @@ -87,14 +88,14 @@ table { } #content { - flex: 1; + flex: 1 auto; padding-top: 10px; display: flex; flex-direction: column; } #sections { - display: flex; + display: flex; flex-direction: row; align-items: center; } @@ -136,7 +137,7 @@ table { #log_section { display: flex; flex-direction: column; - flex: 1; + flex: 1 auto; } #log_section .events { @@ -144,7 +145,7 @@ table { border: 2px solid #e8e8e8; padding-top: 1.875em; position: relative; - flex: 1; + flex: 1 auto; display: flex; flex-direction: column; }