From 4af5295a2b3e4db32bd2e2fe266d8151f44e7abd Mon Sep 17 00:00:00 2001 From: Jakob Friedl <71284620+jakobfriedl@users.noreply.github.com> Date: Tue, 6 May 2025 22:46:36 +0200 Subject: [PATCH] Experiment with TUI implementation using illwill --- .gitignore | 4 ++ server/agent.nim | 19 ------ server/client.nim | 52 --------------- server/config.nims | 3 +- server/index.nim | 9 --- server/listener.nim | 0 server/server.nim | 23 ++----- server/tui.nim | 157 ++++++++++++++++++++++++++++++++++++++++++++ server/urls.nim | 28 -------- 9 files changed, 169 insertions(+), 126 deletions(-) delete mode 100644 server/agent.nim delete mode 100644 server/client.nim delete mode 100644 server/index.nim delete mode 100644 server/listener.nim create mode 100644 server/tui.nim delete mode 100644 server/urls.nim diff --git a/.gitignore b/.gitignore index ed14bb7..9978ff2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Ignore agents +agents/ + + # Nim nimcache/ nimblecache/ diff --git a/server/agent.nim b/server/agent.nim deleted file mode 100644 index db5c63f..0000000 --- a/server/agent.nim +++ /dev/null @@ -1,19 +0,0 @@ -import prologue - -# /agent/register -proc agentRegister*(ctx: Context) {.async.} = - - let body: JsonNode = ctx.request.body().parseJson() - echo body - - resp jsonResponse(body, Http200) - - - -# /agent/{uuid}/tasks -proc agentTasks*(ctx: Context) {.async.} = - resp "

Agent Tasks

" - -# /agent/{uuid}/results -proc agentResults*(ctx: Context) {.async.} = - resp "

Agent Results

" diff --git a/server/client.nim b/server/client.nim deleted file mode 100644 index b0a8e52..0000000 --- a/server/client.nim +++ /dev/null @@ -1,52 +0,0 @@ -import prologue -import logging -import uuids -import strformat -import std/asynchttpserver - -proc hello*(ctx: Context) {.async.} = - resp "Test" - -# /client/listener -proc listenerList*(ctx: Context) {.async.} = - - # JSON Response - let response = %*{"message": "Ok"} - resp jsonResponse(response) - -# /client/listener/create -proc listenerCreate*(ctx: Context) {.async.} = - - # Handle POST parameters (Port, IP) - - # Create listener with random UUID - let - name: string = $genUUID() - listenerSettings = newSettings( - appName = name, - debug = false, - address = "127.0.0.1", - port = Port(443), - secretKey = name - ) - var listener = newApp(settings=listenerSettings) - - proc listenerHandler(req: NativeRequest): Future[void] {.gcsafe.} = - req.respond(Http200, name) - - discard listener.serveAsync(listenerHandler) - logging.info(fmt"Listener {name} created.") - - resp fmt"Listener {name} created.
Listening on {listenerSettings.address}:{listenerSettings.port}" - -# /client/listener/{uuid}/delete -proc listenerDelete*(ctx: Context) {.async.} = - resp "

Listener Deleted

" - -# /client/agent -proc agentList*(ctx: Context) {.async.} = - resp "

Agent List

" - -# /client/agent/build -proc agentCreate*(ctx: Context) {.async.} = - resp "

Agent Create

" diff --git a/server/config.nims b/server/config.nims index e18a816..f177b11 100644 --- a/server/config.nims +++ b/server/config.nims @@ -1,3 +1,2 @@ -# Define command line switches in this file ---define:usestd +# Compiler flags --threads:on \ No newline at end of file diff --git a/server/index.nim b/server/index.nim deleted file mode 100644 index f750699..0000000 --- a/server/index.nim +++ /dev/null @@ -1,9 +0,0 @@ -import prologue - -# / -proc root*(ctx: Context) {.async.} = - resp "

Hello, World!

" - -# /auth -proc auth*(ctx: Context) {.async.} = - resp "

Hello, Auth!

" diff --git a/server/listener.nim b/server/listener.nim deleted file mode 100644 index e69de29..0000000 diff --git a/server/server.nim b/server/server.nim index 4848888..4578e3c 100644 --- a/server/server.nim +++ b/server/server.nim @@ -1,20 +1,11 @@ -import prologue -import asyncdispatch +import ./tui -import ./urls +proc main() = + # Initialize TUI + initUi() -let - env = loadPrologueEnv(".env") - settings = newSettings( - appName = env.getOrDefault("appName", "Prologue"), - debug = env.getOrDefault("debug", true), - port = Port(env.getOrDefault("port", 8080)), - secretKey = env.getOrDefault("secretKey", "") - ) -var app = newApp(settings = settings) -app.addRoute(urls.indexPatterns, "/") -app.addRoute(urls.clientPatterns, "/client") -app.addRoute(urls.agentPatterns, "/agent") -waitFor app.runAsync() \ No newline at end of file + + +main() diff --git a/server/tui.nim b/server/tui.nim new file mode 100644 index 0000000..e619b10 --- /dev/null +++ b/server/tui.nim @@ -0,0 +1,157 @@ +import strformat, strutils, math, times +import illwill +import os + +type + View = enum + BaseView + AgentView + ListenerView + LogView + LootView + + UserInterface = object + tb: TerminalBuffer + view: View + x, y: tuple[start, center, ending: int] + + +#[ + Exit Application +]# +proc exitUi*() {.noconv.} = + illwillDeinit() + showCursor() + quit(0) + +proc renderListenerView(ui: var UserInterface) = + ui.tb.setForegroundColor(fgGreen, bright=false) + ui.tb.drawRect(ui.x.start, 3, ui.tb.width-1, ui.tb.height-2) + +proc renderAgentView(ui: var UserInterface) = + ui.tb.setForegroundColor(fgRed, bright=true) + ui.tb.drawRect(ui.x.start, 3, ui.tb.width-1, ui.tb.height-2) + + +proc renderBaseView(ui: var UserInterface) = + ui.tb.setForegroundColor(fgWhite, bright=false) + ui.tb.drawRect(ui.x.start, 3, ui.tb.width-1, ui.tb.height-2) + + ui.tb.setForegroundColor(fgCyan, bright=false) + ui.tb.write(ui.x.start, 5, fmt"Width: {ui.tb.width}") + ui.tb.write(ui.x.start, 6, fmt"Center: {ui.x.center}") + ui.tb.write(ui.x.start, 7, fmt"Height: {ui.tb.height}") + +#[ + Navigation Menu + TODO: + ~ Refactor using foreach loop over sequence of navbar items + ~ NavItem type: + text: string (pre- and append space automatically) + view: View + fgColor: ForegroundColor + shortcut: Key +]# +proc renderNav(ui: var UserInterface) = + var offset: int = 0 + + var baseNav = newBoxBuffer(ui.tb.width, ui.tb.height) + baseNav.drawRect(ui.x.start, 0, ui.x.start + len(" Base ") + 1, 2, doubleStyle = (ui.view == BaseView)) + ui.tb.setForegroundColor(fgWhite, bright=true) + ui.tb.write(baseNav) + ui.tb.write(ui.x.start + 1, 1, " B", resetStyle, "ase ") + + offset += len(" Base ") + 2 + + var listenerNav = newBoxBuffer(ui.tb.width, ui.tb.height) + listenerNav.drawRect(ui.x.start + offset, 0, ui.x.start + len(" Listeners ") + offset + 1, 2, doubleStyle = (ui.view == ListenerView)) + ui.tb.setForegroundColor(fgGreen) + ui.tb.write(listenerNav) + ui.tb.write(ui.x.start + offset + 1, 1, " L", resetStyle, "isteners ") + + offset += len(" Listeners ") + 2 + + var agentNav = newBoxBuffer(ui.tb.width, ui.tb.height) + agentNav.drawRect(ui.x.start + offset, 0, ui.x.start+len(" Agents ") + offset + 1, 2, doubleStyle = (ui.view == AgentView)) + ui.tb.setForegroundColor(fgRed, bright=true) + ui.tb.write(agentNav) + ui.tb.write(ui.x.start + offset + 1, 1, " A", resetStyle, "gents ") + +proc renderView(ui: var UserInterface) = + case ui.view: + of ListenerView: ui.renderListenerView() + of AgentView: ui.renderAgentView() + else: ui.renderBaseView() + +#[ + Initialize Terminal User Interface +]# + +var input: string = "test" + +proc initUi*() = + + var ui = UserInterface() + + illwillInit(fullscreen=true, mouse=false) + setControlCHook(exitUi) + hideCursor() + + while true: + + let + width = terminalWidth() + height = terminalHeight() + + # Horizontal positioning + ui.x.start = 2 + ui.x.center = cast[int](math.round(width / 2).toInt) - 10 + ui.x.ending = width-1 + + # Vertical positioning + ui.y.start = 4 + ui.y.center = cast[int](math.round(height / 2).toInt) - 2 + ui.y.ending = height-1 + + # Clear screen + ui.tb = newTerminalBuffer(width, height) + + # Header + let date: string = now().format("dd-MM-yyyy HH:mm:ss") + ui.tb.write(ui.x.center, 0, "┏┏┓┏┓┏┓┓┏┏┓┏╋") + ui.tb.write(ui.x.center, 1, "┗┗┛┛┗┗┫┗┻┗ ┛┗ 0.1") + ui.tb.write(ui.x.center, 2, " ┗ @virtualloc") + ui.tb.write(ui.x.ending - len(date), 1, date) + + # Navigation + ui.renderNav() + + # Handle keyboard events + var key: Key = getKey() + case key + of Key.CtrlC: exitUi() + of Key.CtrlL: + ui.view = ListenerView + of Key.CtrlA: + ui.view = AgentView + of Key.CtrlB: + ui.view = BaseView + else: + #[ + TODO: + ~ Turn this into a textbox widget + ]# + if(ord(key) >= 32 and ord(key) < 127): + input &= char(ord(key)) + if(ord(key) == 127 and len(input) >= 1): + input = input[0..len(input)-2] + ui.tb.write(10, 10, input) + + discard + + ui.renderView() + + # Footer + ui.tb.write(ui.x.start, ui.x.ending, "Close using [CTRL+C]") + + ui.tb.display() \ No newline at end of file diff --git a/server/urls.nim b/server/urls.nim deleted file mode 100644 index ee32ff4..0000000 --- a/server/urls.nim +++ /dev/null @@ -1,28 +0,0 @@ -import prologue - -import ./[index, client, agent] - -let indexPatterns* = @[ - pattern("/", index.root, @[HttpGet]), - pattern("/auth", index.auth, @[HttpPost]) -] - -#[ - Client Interfaces -]# -let clientPatterns* = @[ - pattern("/listener/", client.listenerList, @[HttpGet]), - pattern("/listener/create", client.listenerCreate, @[HttpPost, HttpGet]), - pattern("/listener/{uuid}/delete", client.listenerDelete, @[HttpGet]), - pattern("/agent/", client.agentList, @[HttpGet]), - pattern("/agent/create", client.agentCreate, @[HttpPost]) -] - -#[ - Agent API -]# -let agentPatterns* = @[ - pattern("/register", agent.agentRegister, @[HttpPost]), - pattern("/{uuid}/tasks", agent.agentTasks, @[HttpGet, HttpPost]), - pattern("/{uuid}/results", agent.agentResults, @[HttpGet, HttpPost]) -] \ No newline at end of file