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