From 2af01367037a7f63f74103a85778f3f9d5ced72d Mon Sep 17 00:00:00 2001 From: Kai Vogelgesang Date: Wed, 21 Sep 2022 10:33:16 +0200 Subject: [PATCH] Implement monitoring lua client --- lua/cc.d.tl | 4 +- lua/framebuffer.tl | 13 ++- lua/main.tl | 159 ++++++++++++++++++++++++++++++++---- lua/ringbuffer.tl | 41 ++++++++++ lua/types/os.d.tl | 27 ++++++ lua/types/window.d.tl | 1 + server/server/monitoring.py | 19 ++++- 7 files changed, 239 insertions(+), 25 deletions(-) create mode 100644 lua/ringbuffer.tl create mode 100644 lua/types/os.d.tl diff --git a/lua/cc.d.tl b/lua/cc.d.tl index 85046f7..9072ae2 100644 --- a/lua/cc.d.tl +++ b/lua/cc.d.tl @@ -15,4 +15,6 @@ require("types/term") require("types/parallel") require("types/http") require("types/shell") -require("types/window") \ No newline at end of file +require("types/window") +require("types/os") + diff --git a/lua/framebuffer.tl b/lua/framebuffer.tl index c342341..18edb00 100644 --- a/lua/framebuffer.tl +++ b/lua/framebuffer.tl @@ -46,10 +46,12 @@ local function wrap(parent: term.Redirect): Buffer for c = 0, 15 do palette[c+1] = colors.packRGB(parent.getPaletteColor(2^c)) end + local dirty: boolean = false local win = window.create(parent, 1, 1, width, height) local overrides: table = {} + overrides.setCursorPos = function(new_x: integer, new_y: integer) win.setCursorPos(new_x, new_y) x = new_x @@ -84,7 +86,12 @@ local function wrap(parent: term.Redirect): Buffer end overrides.setPaletteColour = overrides.setPaletteColor - local target = setmetatable(overrides, { __index = win }) as term.Redirect + local target = setmetatable(overrides, { + __index = function(_: table, k: any): any + dirty = true + return (win as table)[k] + end + }) as term.Redirect target.setTextColor(colors.white) target.setBackgroundColor(colors.black) @@ -120,10 +127,10 @@ local function wrap(parent: term.Redirect): Buffer end buffer.is_dirty = function(): boolean - return true + return dirty end - buffer.clear_dirty = function() end + buffer.clear_dirty = function() dirty = false end return buffer diff --git a/lua/main.tl b/lua/main.tl index a64d32b..ec6ca82 100644 --- a/lua/main.tl +++ b/lua/main.tl @@ -1,35 +1,158 @@ local json = require("json") local fb = require("framebuffer") -local ENDPOINT = "http://localhost:8000/monitoring" +local ringbuffer = require("ringbuffer") +local UUID = "8b9faf9f-9470-4a50-b405-0af5f0152550" +local ENDPOINT = "ws://localhost:8000/monitoring/computer/" .. UUID .. "/ws" + +print("[MAIN] Init") + +local enum SocketState + "reset" + "connecting" -- currently unused + "ok" +end + +local record Socket + state: SocketState + ws: http.Websocket +end + +local socket: Socket = { + state = "reset", + ws = nil, +} + +local function send(message: string) + -- "message" needs to be valid JSON + -- otherwise the server will not accept it + + if socket.state ~= "ok" then return end + + local r = { pcall(socket.ws.send, message) } + + if r[1] == false then + if (r[2] as string):sub(-11) == "closed file" then + socket.state = "reset" + elseif (r[2] as string):sub(-9) == "too large" then + -- TODO handle + -- the connection stays open though + end + end +end + +-- Set up framebuffer capture and statusline + +print("[MAIN] Setup framebuffer") local orig_native = term.native - local buffer = fb.wrap(orig_native()) - term.native = function(): term.Redirect return buffer.target end -term.redirect(buffer.target as term.Redirect) +local width, height = term.getSize() +local top_line = window.create(buffer.target, 1, 1, width, 1) +local main_view = window.create(buffer.target, 1, 2, width, height - 1) +term.redirect(main_view as term.Redirect) -local function report() +local function set_bar(text: string, fg: string | nil, bg: string | nil) + fg = fg or ("9"):rep(text:len()) + bg = bg or ("f"):rep(text:len()) + top_line.clear() + top_line.setCursorPos(1,1) + top_line.blit(text, fg, bg) + main_view.restoreCursor() +end + +-- Create tasks + +local ws_task = coroutine.create(function() while true do - local body = json.encode({ - screen = buffer.serialize() - }) - local headers = { - ["Content-Type"] = "application/json" - } - local r = { pcall(http.post, ENDPOINT .. "/ping", body, headers) } - sleep(1) + if socket.state == "reset" then + set_bar("[WS] RST", "78870111") + local r = http.websocket(ENDPOINT) + if r ~= false then + socket.ws = r as http.Websocket + set_bar("[WS] OK\x03", "78870DD5") + socket.state = "ok" + else + set_bar("[WS] ERR", "78870EEE") + end + end + repeat + sleep(1) + until socket.state ~= "ok" + end +end) + +local report_task = coroutine.create(function() + local last_report = -1.0 + while true do + local now = os.clock() + if now - last_report >= 0.05 then + local message = json.encode({ + screen = buffer.serialize() + }) + send(message) + last_report = now + end + sleep(0) -- until next gametick + end +end) + +local shell_task = coroutine.create(function() + shell.run("shell") +end) + +-- basically parallel.waitForAny + +local record Task + coro: thread + filter: string | nil +end + +local tasks: {Task} = { + {coro = shell_task}, -- pid 1 + {coro = ws_task}, + {coro = report_task}, +} + +local event_queue = ringbuffer.new(64) +event_queue:push({n = 0}) + +local shell_running = true +while shell_running do + local e: table + + if not event_queue:is_empty() then + e = event_queue:pop() as table + else + e = table.pack(os.pullEventRaw()) + end + + for pid = 1, #tasks do + local task = tasks[pid] + if task.filter == nil or task.filter == e[1] or e[1] == "terminate" then + local ok, param = coroutine.resume(task.coro, table.unpack(e as {any})) + if not ok then + term.redirect(orig_native()) + term.clear() + term.setCursorPos(1,1) + print("OMEGABIG OOF") + print(("pid %d"):format(pid)) + error(param, 0) + else + task.filter = param as string + end + if pid == 1 and coroutine.status(task.coro) == "dead" then + shell_running = false + end + end end end -local function run_shell() - shell.run("shell") -end - -parallel.waitForAny(report, run_shell) term.native = orig_native term.redirect(term.native()) +term.clear() +term.setCursorPos(1,1) \ No newline at end of file diff --git a/lua/ringbuffer.tl b/lua/ringbuffer.tl new file mode 100644 index 0000000..ca7afe0 --- /dev/null +++ b/lua/ringbuffer.tl @@ -0,0 +1,41 @@ +local record Ringbuffer + {T} + push: function(self: Ringbuffer, el: T): boolean + pop: function(self: Ringbuffer): T | nil + is_empty: function(self: Ringbuffer): boolean + + head: integer + n: integer + size: integer +end + +local impl: table = {} + +impl.push = function(self: Ringbuffer, el: T): boolean + if self.n == self.size then return false end + -- items are at head + 0, head + 1, ..., head + (n-1) + local tail = (self.head + self.n) % self.size + self[1 + tail] = el + self.n = self.n + 1 + return true +end + +impl.pop = function(self: Ringbuffer): T | nil + if self.n == 0 then return nil end + local res = self[1 + self.head] + self.head = (self.head + 1) % self.size + self.n = self.n - 1 + return res +end + +impl.is_empty = function(self: Ringbuffer): boolean + return self.n == 0 +end + +local function new(size: integer): Ringbuffer + return setmetatable({ head = 0, n = 0, size = size }, { __index = impl }) +end + +return { + new = new +} \ No newline at end of file diff --git a/lua/types/os.d.tl b/lua/types/os.d.tl new file mode 100644 index 0000000..8d4e75f --- /dev/null +++ b/lua/types/os.d.tl @@ -0,0 +1,27 @@ +global record os + pullEvent: function(filter: string | nil): string, any... + pullEventRaw: function(filter: string | nil): string, any... + sleep: function(time: number) + version: function(): string + run: function(env: table, path: string, ...: any): boolean + queueEvent: function(name: string, ...: any) + startTimer: function(time: number): integer + cancelTimer: function(token: integer) + setAlarm: function(time: number): integer + cancelAlarm: function(token: integer) + shutdown: function() + reboot: function() + getComputerID: function(): integer + computerID: function(): integer + getComputerLabel: function(): string + computerLabel: function(): string + setComputerLabel: function(label: string | nil) + clock: function(): number + time: function(locale: string | nil): number + time: function(locale: table): integer + day: function(args: string | nil): integer + epoch: function(args: string | nil): integer + date: function(): string + date: function(format: string): string | table + date: function(format: string, time: number): string | table +end \ No newline at end of file diff --git a/lua/types/window.d.tl b/lua/types/window.d.tl index afd6d68..91730d5 100644 --- a/lua/types/window.d.tl +++ b/lua/types/window.d.tl @@ -31,6 +31,7 @@ global record window setVisible: function(visible: boolean) isVisible: function(): boolean redraw: function() + restoreCursor: function() getPosition: function(): integer, integer reposition: function(new_x: integer, new_y: integer, new_width: integer | nil, new_height: integer | nil, new_parent: term.Redirect | nil) end diff --git a/server/server/monitoring.py b/server/server/monitoring.py index 933d645..a55fd56 100644 --- a/server/server/monitoring.py +++ b/server/server/monitoring.py @@ -1,6 +1,7 @@ -import binascii +import json +from uuid import UUID -from fastapi import FastAPI, Request +from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect from pydantic import BaseModel monitoring = FastAPI() @@ -22,4 +23,16 @@ class Ping(BaseModel): async def ping(request: Request, data: Ping): print("[PING]") for line in data.screen.text: - print(line) \ No newline at end of file + print(line) + +@monitoring.websocket("/computer/{uuid}/ws") +async def computer_ws(socket: WebSocket, uuid: UUID): + await socket.accept() + print(f"[WS] Computer {uuid} connected") + while True: + try: + data = await socket.receive_json() + #print(f"[WS] rx {json.dumps(data)}") + except WebSocketDisconnect: + break + print(f"[WS] Computer {uuid} disconnected") \ No newline at end of file