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.
This commit is contained in:
Zahary Karadjov 2018-11-27 13:50:56 +02:00
parent 8b4e8e174b
commit 3b06528906
6 changed files with 106 additions and 29 deletions

View File

@ -1,5 +1,8 @@
import import
karax/vdom jsffi, karax/[karax, vdom]
export
karax, vdom
type type
SectionRenderer* = proc(): VNode SectionRenderer* = proc(): VNode
@ -8,12 +11,68 @@ type
title*: cstring title*: cstring
content*: SectionRenderer 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): when defined(createChroniclesTail):
proc getKarax*: KaraxInstance {.exportc.} = kxi
var var
sections* = newSeq[Section](0) sections* = newSeq[Section](0)
filters* = newSeq[TailEventFilter]()
proc addSection*(title: cstring, content: SectionRenderer) {.exportc.} = proc addSection*(title: cstring, content: SectionRenderer) {.exportc.} =
sections.add Section(title: title, content: content) sections.add Section(title: title, content: content)
else: redrawSync()
proc addSection*(title: cstring, content: SectionRenderer) {.importc.}
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("&lt;")
of '>': result.add("&gt;")
of '&': result.add("&amp;")
of '"': result.add("&quot;")
of '\'': result.add("&#x27;")
of '/': result.add("&#x2F;")
else: result.add(c)
proc addAsHtml*(result: var string, obj: js) {.exportc.} =
if jsTypeOf(obj) == "object":
result.add "<table>"
for key, value in obj:
result.add """<tr><td class = "key">"""
result.addEscaped $key
result.add """</td><td class = "value">"""
result.addAsHtml value
result.add "</td></tr>"
result.add "</table>"
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.}

View File

@ -1 +1,2 @@
: karax_app.nim |> nim --hotCodeReloading:on -d:createChroniclesTail -o:%o js %f |> %B.js : 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

View File

@ -8,7 +8,10 @@
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
</head> </head>
<body> <body>
<div id="ROOT" /> <div>
<div id="ROOT"></div>
</div>
<script src="karax_app.js"></script> <script src="karax_app.js"></script>
<script src="eth_p2p_plugin.js"></script>
</body> </body>
</html> </html>

View File

@ -3,7 +3,7 @@ include
import import
jsconsole, jscore, jsffi, jswebsockets, jsconsole, jscore, jsffi, jswebsockets,
tail_jsplugins chronicles_tail/jsplugins
type type
ChroniclesPrompt = object ChroniclesPrompt = object
@ -11,13 +11,11 @@ type
suggestions: seq[string] suggestions: seq[string]
activeSuggestionIdx: int activeSuggestionIdx: int
LogEvent = js
var var
chroniclesCredentials* {.importc, nodecl.}: js chroniclesCredentials* {.importc, nodecl.}: js
chroniclesPrompt: ChroniclesPrompt chroniclesPrompt: ChroniclesPrompt
activeSectionIdx = 0 activeSectionIdx = 0
logEvents = newSeq[LogEvent]() logEvents = newSeq[TailEvent]()
proc activeOrInactive(idx, currentActiveIdx: int): cstring = proc activeOrInactive(idx, currentActiveIdx: int): cstring =
if idx == currentActiveIdx: "active" if idx == currentActiveIdx: "active"
@ -48,14 +46,12 @@ proc logSectionContent: VNode =
tbody: tbody:
for event in logEvents: for event in logEvents:
let level = cast[cstring](event.level) let level = event.level
tr(class = level): tr(class = level):
td(class = "level"): text level td(class = "level"): text level
td(class = "ts") : text cast[cstring](event.ts) td(class = "ts") : text event.ts
td(class = "msg") : text cast[cstring](event.msg) td(class = "msg") : text event.msg
td(class = "props"): text cast[cstring](event.x) td(class = "props"): text event.data.asHtml
addSection("Log", logSectionContent)
var chroniclesSocket = newWebSocket(cast[cstring](chroniclesCredentials.url)) var chroniclesSocket = newWebSocket(cast[cstring](chroniclesCredentials.url))
@ -63,8 +59,13 @@ chroniclesSocket.onOpen = proc (e: jswebsockets.Event) =
chroniclesSocket.send(cast[cstring](chroniclesCredentials.accessToken)) chroniclesSocket.send(cast[cstring](chroniclesCredentials.accessToken))
chroniclesSocket.onMessage = proc (e: MessageEvent) = chroniclesSocket.onMessage = proc (e: MessageEvent) =
var msg = JSON.parse(e.data).js var msg = cast[TailEvent](JSON.parse(e.data))
if msg.level == jsundefined:
for f in filters:
if f(msg):
return
if msg.level.toJs == jsundefined:
console.log msg console.log msg
return return
@ -100,4 +101,5 @@ proc pageContent(): VNode =
activeSection() activeSection()
setRenderer pageContent setRenderer pageContent
addSection "Log", logSectionContent

View File

@ -1,12 +1,19 @@
import import
macros, json, random, times, strutils, macros, json, random, times, strutils, os,
asynchttpserver, asyncnet, asyncdispatch, websocket, asynchttpserver, asyncnet, asyncdispatch, websocket,
chronicles_tail/configuration chronicles_tail/configuration
const const
indexFile = staticRead "karax_app.html" indexFile = staticRead "karax_app.html"
faviconBytes = staticRead "favicon.png" 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 type
WebuiServer* = ref object WebuiServer* = ref object
@ -45,7 +52,7 @@ proc serve*(s: WebuiServer): Future[void] =
var nextLineToSend = 0 var nextLineToSend = 0
while nextLineToSend < s.logLines.len: while nextLineToSend < s.logLines.len:
await ws.sendText(s.logLines[nextLineToSend]) await ws.sendText(s.logLines[nextLineToSend], maskingKey = "")
inc nextLineToSend inc nextLineToSend
s.clients.add ws s.clients.add ws
@ -54,9 +61,13 @@ proc serve*(s: WebuiServer): Future[void] =
await req.respond(Http200, indexFile) await req.respond(Http200, indexFile)
of "/styles.css": of "/styles.css":
await req.respond(Http200, styles, await req.respond(Http200, fileContents("styles.css"),
newHttpHeaders({"Content-Type": "text/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": of "/karax_app.js":
var scripts = newStringOfCap(16000) var scripts = newStringOfCap(16000)
@ -69,8 +80,8 @@ proc serve*(s: WebuiServer): Future[void] =
scripts.add compileJs("karax_app.nim", "-d:createChroniclesTail") scripts.add compileJs("karax_app.nim", "-d:createChroniclesTail")
for plugin in s.plugins: # for plugin in s.plugins:
scripts.add plugin # scripts.add plugin
await req.respond(Http200, scripts, await req.respond(Http200, scripts,
newHttpHeaders({"Content-Type": "application/javascript"})) newHttpHeaders({"Content-Type": "application/javascript"}))

View File

@ -71,15 +71,16 @@ table {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
color: white; color: white;
flex: 0; flex: 0 auto;
font-size: 16px;
} }
#header h1 { #header h1 {
justify-self: flex-end; justify-self: flex-end;
order: 3; order: 3;
text-align: right; text-align: right;
flex: 1; flex: 1 auto;
font-size: 13px; font-size: 15px;
} }
#content, #header { #content, #header {
@ -87,7 +88,7 @@ table {
} }
#content { #content {
flex: 1; flex: 1 auto;
padding-top: 10px; padding-top: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -136,7 +137,7 @@ table {
#log_section { #log_section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1 auto;
} }
#log_section .events { #log_section .events {
@ -144,7 +145,7 @@ table {
border: 2px solid #e8e8e8; border: 2px solid #e8e8e8;
padding-top: 1.875em; padding-top: 1.875em;
position: relative; position: relative;
flex: 1; flex: 1 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }