Implement monitoring lua client
This commit is contained in:
parent
1fde73778b
commit
2af0136703
@ -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")
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
159
lua/main.tl
159
lua/main.tl
@ -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
41
lua/ringbuffer.tl
Normal 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
27
lua/types/os.d.tl
Normal 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
|
@ -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
|
||||
|
@ -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")
|
Loading…
Reference in New Issue
Block a user