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/parallel")
|
||||||
require("types/http")
|
require("types/http")
|
||||||
require("types/shell")
|
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
|
for c = 0, 15 do
|
||||||
palette[c+1] = colors.packRGB(parent.getPaletteColor(2^c))
|
palette[c+1] = colors.packRGB(parent.getPaletteColor(2^c))
|
||||||
end
|
end
|
||||||
|
local dirty: boolean = false
|
||||||
|
|
||||||
local win = window.create(parent, 1, 1, width, height)
|
local win = window.create(parent, 1, 1, width, height)
|
||||||
|
|
||||||
local overrides: table = {}
|
local overrides: table = {}
|
||||||
|
|
||||||
overrides.setCursorPos = function(new_x: integer, new_y: integer)
|
overrides.setCursorPos = function(new_x: integer, new_y: integer)
|
||||||
win.setCursorPos(new_x, new_y)
|
win.setCursorPos(new_x, new_y)
|
||||||
x = new_x
|
x = new_x
|
||||||
@ -84,7 +86,12 @@ local function wrap(parent: term.Redirect): Buffer
|
|||||||
end
|
end
|
||||||
overrides.setPaletteColour = overrides.setPaletteColor
|
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.setTextColor(colors.white)
|
||||||
target.setBackgroundColor(colors.black)
|
target.setBackgroundColor(colors.black)
|
||||||
@ -120,10 +127,10 @@ local function wrap(parent: term.Redirect): Buffer
|
|||||||
end
|
end
|
||||||
|
|
||||||
buffer.is_dirty = function(): boolean
|
buffer.is_dirty = function(): boolean
|
||||||
return true
|
return dirty
|
||||||
end
|
end
|
||||||
|
|
||||||
buffer.clear_dirty = function() end
|
buffer.clear_dirty = function() dirty = false end
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
|
|
||||||
|
159
lua/main.tl
159
lua/main.tl
@ -1,35 +1,158 @@
|
|||||||
local json = require("json")
|
local json = require("json")
|
||||||
local fb = require("framebuffer")
|
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 orig_native = term.native
|
||||||
|
|
||||||
local buffer = fb.wrap(orig_native())
|
local buffer = fb.wrap(orig_native())
|
||||||
|
|
||||||
term.native = function(): term.Redirect
|
term.native = function(): term.Redirect
|
||||||
return buffer.target
|
return buffer.target
|
||||||
end
|
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
|
while true do
|
||||||
local body = json.encode({
|
if socket.state == "reset" then
|
||||||
screen = buffer.serialize()
|
set_bar("[WS] RST", "78870111")
|
||||||
})
|
local r = http.websocket(ENDPOINT)
|
||||||
local headers = {
|
if r ~= false then
|
||||||
["Content-Type"] = "application/json"
|
socket.ws = r as http.Websocket
|
||||||
}
|
set_bar("[WS] OK\x03", "78870DD5")
|
||||||
local r = { pcall(http.post, ENDPOINT .. "/ping", body, headers) }
|
socket.state = "ok"
|
||||||
sleep(1)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run_shell()
|
|
||||||
shell.run("shell")
|
|
||||||
end
|
|
||||||
|
|
||||||
parallel.waitForAny(report, run_shell)
|
|
||||||
|
|
||||||
term.native = orig_native
|
term.native = orig_native
|
||||||
term.redirect(term.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)
|
setVisible: function(visible: boolean)
|
||||||
isVisible: function(): boolean
|
isVisible: function(): boolean
|
||||||
redraw: function()
|
redraw: function()
|
||||||
|
restoreCursor: function()
|
||||||
getPosition: function(): integer, integer
|
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)
|
reposition: function(new_x: integer, new_y: integer, new_width: integer | nil, new_height: integer | nil, new_parent: term.Redirect | nil)
|
||||||
end
|
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
|
from pydantic import BaseModel
|
||||||
monitoring = FastAPI()
|
monitoring = FastAPI()
|
||||||
|
|
||||||
@ -22,4 +23,16 @@ class Ping(BaseModel):
|
|||||||
async def ping(request: Request, data: Ping):
|
async def ping(request: Request, data: Ping):
|
||||||
print("[PING]")
|
print("[PING]")
|
||||||
for line in data.screen.text:
|
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