Implement monitoring lua client

This commit is contained in:
Kai Vogelgesang 2022-09-21 10:33:16 +02:00
parent 1fde73778b
commit 2af0136703
Signed by: kai
GPG Key ID: 0A95D3B6E62C0879
7 changed files with 239 additions and 25 deletions

View File

@ -15,4 +15,6 @@ require("types/term")
require("types/parallel")
require("types/http")
require("types/shell")
require("types/window")
require("types/window")
require("types/os")

View File

@ -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

View File

@ -1,35 +1,158 @@
local json = require("json")
local fb = require("framebuffer")
local ENDPOINT <const> = "http://localhost:8000/monitoring"
local ringbuffer = require("ringbuffer")
local UUID <const> = "8b9faf9f-9470-4a50-b405-0af5f0152550"
local ENDPOINT <const> = "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)

41
lua/ringbuffer.tl Normal file
View File

@ -0,0 +1,41 @@
local record Ringbuffer<T>
{T}
push: function(self: Ringbuffer<T>, el: T): boolean
pop: function(self: Ringbuffer<T>): T | nil
is_empty: function(self: Ringbuffer<T>): boolean
head: integer
n: integer
size: integer
end
local impl: table = {}
impl.push = function<T>(self: Ringbuffer<T>, 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<T>(self: Ringbuffer<T>): 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<T>(self: Ringbuffer<T>): boolean
return self.n == 0
end
local function new<T>(size: integer): Ringbuffer<T>
return setmetatable({ head = 0, n = 0, size = size }, { __index = impl })
end
return {
new = new
}

27
lua/types/os.d.tl Normal file
View File

@ -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

View File

@ -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

View File

@ -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)
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")