Implement computer registration
This commit is contained in:
parent
227b9ba5dc
commit
63d6fe4b26
294
backend/lua/framebuffer.lua
Normal file
294
backend/lua/framebuffer.lua
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
--- https://github.com/SquidDev-CC/cloud-catcher/blob/master/src/host/framebuffer.lua
|
||||||
|
--- but slightly modified because this isn't cloud catcher
|
||||||
|
|
||||||
|
--- Just another frame buffer, but this one is serialisable!
|
||||||
|
|
||||||
|
local stringify = require("json").stringify
|
||||||
|
|
||||||
|
local colour_lookup = {}
|
||||||
|
for i = 0, 15 do
|
||||||
|
colour_lookup[2 ^ i] = string.format("%x", i)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--- Create a buffer which can be converted to a string and transmitted.
|
||||||
|
local function buffer(original)
|
||||||
|
local text = {}
|
||||||
|
local text_colour = {}
|
||||||
|
local back_colour = {}
|
||||||
|
local palette = {}
|
||||||
|
local palette_24 = {}
|
||||||
|
|
||||||
|
local cursor_x, cursor_y = 1, 1
|
||||||
|
|
||||||
|
local cursor_blink = false
|
||||||
|
local cur_text_colour = "0"
|
||||||
|
local cur_back_colour = "f"
|
||||||
|
|
||||||
|
local sizeX, sizeY = original.getSize()
|
||||||
|
local color = original.isColor()
|
||||||
|
|
||||||
|
local dirty = false
|
||||||
|
|
||||||
|
local redirect = {}
|
||||||
|
|
||||||
|
if original.getPaletteColour then
|
||||||
|
for i = 0, 15 do
|
||||||
|
local c = 2 ^ i
|
||||||
|
palette[c] = { original.getPaletteColour( c ) }
|
||||||
|
palette_24[colour_lookup[c]] = colours.rgb8(original.getPaletteColour( c ))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.write(writeText)
|
||||||
|
writeText = tostring(writeText)
|
||||||
|
original.write(writeText)
|
||||||
|
dirty = true
|
||||||
|
|
||||||
|
-- If we're off the screen then just emulate a write
|
||||||
|
if cursor_y > sizeY or cursor_y < 1 or cursor_x + #writeText <= 1 or cursor_x > sizeX then
|
||||||
|
cursor_x = cursor_x + #writeText
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Adjust text to fit on screen
|
||||||
|
if cursor_x < 1 then
|
||||||
|
writeText = writeText:sub(-cursor_x + 2)
|
||||||
|
cursor_x = 1
|
||||||
|
elseif cursor_x + #writeText > sizeX then
|
||||||
|
writeText = writeText:sub(1, sizeX - cursor_x + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local lineText = text[cursor_y]
|
||||||
|
local lineColor = text_colour[cursor_y]
|
||||||
|
local lineBack = back_colour[cursor_y]
|
||||||
|
local preStop = cursor_x - 1
|
||||||
|
local preStart = math.min(1, preStop)
|
||||||
|
local postStart = cursor_x + #writeText
|
||||||
|
local postStop = sizeX
|
||||||
|
local sub, rep = string.sub, string.rep
|
||||||
|
|
||||||
|
text[cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop)
|
||||||
|
text_colour[cursor_y] = sub(lineColor, preStart, preStop)..rep(cur_text_colour, #writeText)..sub(lineColor, postStart, postStop)
|
||||||
|
back_colour[cursor_y] = sub(lineBack, preStart, preStop)..rep(cur_back_colour, #writeText)..sub(lineBack, postStart, postStop)
|
||||||
|
cursor_x = cursor_x + #writeText
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.blit(writeText, writeFore, writeBack)
|
||||||
|
original.blit(writeText, writeFore, writeBack)
|
||||||
|
dirty = true
|
||||||
|
|
||||||
|
-- If we're off the screen then just emulate a write
|
||||||
|
if cursor_y > sizeY or cursor_y < 1 or cursor_x + #writeText <= 1 or cursor_x > sizeX then
|
||||||
|
cursor_x = cursor_x + #writeText
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if cursor_x < 1 then
|
||||||
|
--adjust text to fit on screen starting at one.
|
||||||
|
writeText = writeText:sub(-cursor_x + 2)
|
||||||
|
writeFore = writeFore:sub(-cursor_x + 2)
|
||||||
|
writeBack = writeBack:sub(-cursor_x + 2)
|
||||||
|
cursor_x = 1
|
||||||
|
elseif cursor_x + #writeText > sizeX then
|
||||||
|
writeText = writeText:sub(1, sizeX - cursor_x + 1)
|
||||||
|
writeFore = writeFore:sub(1, sizeX - cursor_x + 1)
|
||||||
|
writeBack = writeBack:sub(1, sizeX - cursor_x + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local lineText = text[cursor_y]
|
||||||
|
local lineColor = text_colour[cursor_y]
|
||||||
|
local lineBack = back_colour[cursor_y]
|
||||||
|
local preStop = cursor_x - 1
|
||||||
|
local preStart = math.min(1, preStop)
|
||||||
|
local postStart = cursor_x + #writeText
|
||||||
|
local postStop = sizeX
|
||||||
|
local sub = string.sub
|
||||||
|
|
||||||
|
text[cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop)
|
||||||
|
text_colour[cursor_y] = sub(lineColor, preStart, preStop)..writeFore..sub(lineColor, postStart, postStop)
|
||||||
|
back_colour[cursor_y] = sub(lineBack, preStart, preStop)..writeBack..sub(lineBack, postStart, postStop)
|
||||||
|
cursor_x = cursor_x + #writeText
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.clear()
|
||||||
|
for i = 1, sizeY do
|
||||||
|
text[i] = string.rep(" ", sizeX)
|
||||||
|
text_colour[i] = string.rep(cur_text_colour, sizeX)
|
||||||
|
back_colour[i] = string.rep(cur_back_colour, sizeX)
|
||||||
|
end
|
||||||
|
|
||||||
|
dirty = true
|
||||||
|
return original.clear()
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.clearLine()
|
||||||
|
-- If we're off the screen then just emulate a clearLine
|
||||||
|
if cursor_y > sizeY or cursor_y < 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
text[cursor_y] = string.rep(" ", sizeX)
|
||||||
|
text_colour[cursor_y] = string.rep(cur_text_colour, sizeX)
|
||||||
|
back_colour[cursor_y] = string.rep(cur_back_colour, sizeX)
|
||||||
|
|
||||||
|
dirty = true
|
||||||
|
return original.clearLine()
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.getCursorPos()
|
||||||
|
return cursor_x, cursor_y
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.setCursorPos(x, y)
|
||||||
|
if type(x) ~= "number" then error("bad argument #1 (expected number, got " .. type(x) .. ")", 2) end
|
||||||
|
if type(y) ~= "number" then error("bad argument #2 (expected number, got " .. type(y) .. ")", 2) end
|
||||||
|
|
||||||
|
if x ~= cursor_x or y ~= cursor_y then
|
||||||
|
cursor_x = math.floor(x)
|
||||||
|
cursor_y = math.floor(y)
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return original.setCursorPos(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.setCursorBlink(b)
|
||||||
|
if type(b) ~= "boolean" then error("bad argument #1 (expected boolean, got " .. type(b) .. ")", 2) end
|
||||||
|
|
||||||
|
if cursor_blink ~= b then
|
||||||
|
cursor_blink = b
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return original.setCursorBlink(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.getSize()
|
||||||
|
return sizeX, sizeY
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.scroll(n)
|
||||||
|
if type(n) ~= "number" then error("bad argument #1 (expected number, got " .. type(n) .. ")", 2) end
|
||||||
|
|
||||||
|
local empty_text = string.rep(" ", sizeX)
|
||||||
|
local empty_text_colour = string.rep(cur_text_colour, sizeX)
|
||||||
|
local empty_back_colour = string.rep(cur_back_colour, sizeX)
|
||||||
|
if n > 0 then
|
||||||
|
for i = 1, sizeY do
|
||||||
|
text[i] = text[i + n] or empty_text
|
||||||
|
text_colour[i] = text_colour[i + n] or empty_text_colour
|
||||||
|
back_colour[i] = back_colour[i + n] or empty_back_colour
|
||||||
|
end
|
||||||
|
elseif n < 0 then
|
||||||
|
for i = sizeY, 1, -1 do
|
||||||
|
text[i] = text[i + n] or empty_text
|
||||||
|
text_colour[i] = text_colour[i + n] or empty_text_colour
|
||||||
|
back_colour[i] = back_colour[i + n] or empty_back_colour
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
dirty = true
|
||||||
|
return original.scroll(n)
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.setTextColour(clr)
|
||||||
|
if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end
|
||||||
|
local new_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2)
|
||||||
|
|
||||||
|
if new_colour ~= cur_text_colour then
|
||||||
|
dirty = true
|
||||||
|
cur_text_colour = new_colour
|
||||||
|
end
|
||||||
|
|
||||||
|
return original.setTextColour(clr)
|
||||||
|
end
|
||||||
|
redirect.setTextColor = redirect.setTextColour
|
||||||
|
|
||||||
|
function redirect.setBackgroundColour(clr)
|
||||||
|
if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end
|
||||||
|
local new_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2)
|
||||||
|
|
||||||
|
if new_colour ~= cur_back_colour then
|
||||||
|
dirty = true
|
||||||
|
cur_back_colour = new_colour
|
||||||
|
end
|
||||||
|
|
||||||
|
return original.setBackgroundColour(clr)
|
||||||
|
end
|
||||||
|
redirect.setBackgroundColor = redirect.setBackgroundColour
|
||||||
|
|
||||||
|
function redirect.isColour()
|
||||||
|
return color == true
|
||||||
|
end
|
||||||
|
redirect.isColor = redirect.isColour
|
||||||
|
|
||||||
|
function redirect.getTextColour()
|
||||||
|
return 2 ^ tonumber(cur_text_colour, 16)
|
||||||
|
end
|
||||||
|
redirect.getTextColor = redirect.getTextColour
|
||||||
|
|
||||||
|
function redirect.getBackgroundColour()
|
||||||
|
return 2 ^ tonumber(cur_back_colour, 16)
|
||||||
|
end
|
||||||
|
redirect.getBackgroundColor = redirect.getBackgroundColour
|
||||||
|
|
||||||
|
if original.getPaletteColour then
|
||||||
|
function redirect.setPaletteColour(colour, r, g, b)
|
||||||
|
local palcol = palette[colour]
|
||||||
|
if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end
|
||||||
|
|
||||||
|
if type(r) == "number" and g == nil and b == nil then
|
||||||
|
palcol[1], palcol[2], palcol[3] = colours.rgb8(r)
|
||||||
|
palette_24[colour] = r
|
||||||
|
else
|
||||||
|
if type(r) ~= "number" then error("bad argument #2 (expected number, got " .. type(r) .. ")", 2) end
|
||||||
|
if type(g) ~= "number" then error("bad argument #3 (expected number, got " .. type(g) .. ")", 2) end
|
||||||
|
if type(b) ~= "number" then error("bad argument #4 (expected number, got " .. type(b ) .. ")", 2 ) end
|
||||||
|
|
||||||
|
palcol[1], palcol[2], palcol[3] = r, g, b
|
||||||
|
palette_24[colour_lookup[colour]] = colours.rgb8(r, g, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
dirty = true
|
||||||
|
return original.setPaletteColour(colour, r, g, b)
|
||||||
|
end
|
||||||
|
redirect.setPaletteColor = redirect.setPaletteColour
|
||||||
|
|
||||||
|
function redirect.getPaletteColour(colour)
|
||||||
|
local palcol = palette[colour]
|
||||||
|
if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end
|
||||||
|
return palcol[1], palcol[2], palcol[3]
|
||||||
|
end
|
||||||
|
redirect.getPaletteColor = redirect.getPaletteColour
|
||||||
|
end
|
||||||
|
|
||||||
|
function redirect.is_dirty() return dirty end
|
||||||
|
function redirect.clear_dirty() dirty = false end
|
||||||
|
|
||||||
|
function redirect.serialize()
|
||||||
|
local palette = {}
|
||||||
|
for i = 0, 15 do
|
||||||
|
palette[i+1] = string.format("#%06x", palette_24[string.format("%x", i)])
|
||||||
|
end
|
||||||
|
|
||||||
|
return stringify { framebuffer = {
|
||||||
|
width = sizeX, height = sizeY,
|
||||||
|
cursorX = cursor_x, cursorY = cursor_y, cursorBlink = cursor_blink,
|
||||||
|
-- curFg = cur_text_colour, curBg = cur_back_colour,
|
||||||
|
|
||||||
|
palette = palette,
|
||||||
|
textBuf = text, fgBuf = text_colour, bgBuf = back_colour
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ensure we're in sync with the parent terminal
|
||||||
|
redirect.setCursorPos(1, 1)
|
||||||
|
redirect.setBackgroundColor(colours.black)
|
||||||
|
redirect.setTextColor(colours.white)
|
||||||
|
redirect.clear()
|
||||||
|
|
||||||
|
return redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
return { buffer = buffer }
|
169
backend/lua/json.lua
Normal file
169
backend/lua/json.lua
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
local tonumber = tonumber
|
||||||
|
|
||||||
|
local function skip_delim(str, pos, delim, err_if_missing)
|
||||||
|
pos = pos + #str:match('^%s*', pos)
|
||||||
|
if str:sub(pos, pos) ~= delim then
|
||||||
|
if err_if_missing then error('Expected ' .. delim) end
|
||||||
|
return pos, false
|
||||||
|
end
|
||||||
|
return pos + 1, true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A table of JSON->Lua escape characters
|
||||||
|
local esc_map = { b = '\b', f = '\f', n = '\n', r = '\r', t = '\t' }
|
||||||
|
|
||||||
|
local function parse_str_val(str, pos)
|
||||||
|
local out, n = {}, 0
|
||||||
|
if pos > #str then error("Malformed JSON (in string)") end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local c = str:sub(pos, pos)
|
||||||
|
if c == '"' then return table.concat(out, "", 1, n), pos + 1 end
|
||||||
|
|
||||||
|
n = n + 1
|
||||||
|
if c == '\\' then
|
||||||
|
local nextc = str:sub(pos + 1, pos + 1)
|
||||||
|
if not nextc then error("Malformed JSON (in string)") end
|
||||||
|
if nextc == "u" then
|
||||||
|
local num = tonumber(str:sub(pos + 2, pos + 5), 16)
|
||||||
|
if not num then error("Malformed JSON (in unicode string) ") end
|
||||||
|
if num <= 255 then
|
||||||
|
pos, out[n] = pos + 6, string.char(num)
|
||||||
|
else
|
||||||
|
pos, out[n] = pos + 6, "?"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
pos, out[n] = pos + 2, esc_map[nextc] or nextc
|
||||||
|
end
|
||||||
|
else
|
||||||
|
pos, out[n] = pos + 1, c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_num_val(str, pos)
|
||||||
|
local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
|
||||||
|
local val = tonumber(num_str)
|
||||||
|
if not val then error('Error parsing number at position ' .. pos .. '.') end
|
||||||
|
return val, pos + #num_str
|
||||||
|
end
|
||||||
|
|
||||||
|
local null = {}
|
||||||
|
local literals = {['true'] = true, ['false'] = false, ['null'] = null }
|
||||||
|
|
||||||
|
-- Build a table of Lua->JSON escape characters
|
||||||
|
local escapes = {}
|
||||||
|
for i = 0, 255 do
|
||||||
|
local c = string.char(i)
|
||||||
|
if i >= 32 and i <= 126
|
||||||
|
then escapes[c] = c
|
||||||
|
else escapes[c] = ("\\u00%02x"):format(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
escapes["\t"], escapes["\n"], escapes["\r"], escapes["\""], escapes["\\"] = "\\t", "\\n", "\\r", "\\\"", "\\\\"
|
||||||
|
|
||||||
|
local function parse(str, pos, end_delim)
|
||||||
|
pos = pos or 1
|
||||||
|
if pos > #str then error('Reached unexpected end of input.') end
|
||||||
|
local pos = pos + #str:match('^%s*', pos)
|
||||||
|
local first = str:sub(pos, pos)
|
||||||
|
if first == '{' then
|
||||||
|
local obj, key, delim_found = {}, true, true
|
||||||
|
pos = pos + 1
|
||||||
|
while true do
|
||||||
|
key, pos = parse(str, pos, '}')
|
||||||
|
if key == nil then return obj, pos end
|
||||||
|
if not delim_found then error('Comma missing between object items.') end
|
||||||
|
pos = skip_delim(str, pos, ':', true)
|
||||||
|
obj[key], pos = parse(str, pos)
|
||||||
|
pos, delim_found = skip_delim(str, pos, ',')
|
||||||
|
end
|
||||||
|
elseif first == '[' then
|
||||||
|
local arr, val, delim_found = {}, true, true
|
||||||
|
pos = pos + 1
|
||||||
|
while true do
|
||||||
|
val, pos = parse(str, pos, ']')
|
||||||
|
if val == nil then return arr, pos end
|
||||||
|
if not delim_found then error('Comma missing between array items.') end
|
||||||
|
arr[#arr + 1] = val
|
||||||
|
pos, delim_found = skip_delim(str, pos, ',')
|
||||||
|
end
|
||||||
|
elseif first == '"' then
|
||||||
|
return parse_str_val(str, pos + 1)
|
||||||
|
elseif first == '-' or first:match('%d') then
|
||||||
|
return parse_num_val(str, pos)
|
||||||
|
elseif first == end_delim then
|
||||||
|
return nil, pos + 1
|
||||||
|
else
|
||||||
|
for lit_str, lit_val in pairs(literals) do
|
||||||
|
local lit_end = pos + #lit_str - 1
|
||||||
|
if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
|
||||||
|
end
|
||||||
|
local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
|
||||||
|
error('Invalid json syntax starting at ' .. pos_info_str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local format, gsub, tostring, pairs, next, type, concat
|
||||||
|
= string.format, string.gsub, tostring, pairs, next, type, table.concat
|
||||||
|
|
||||||
|
local function stringify_impl(t, out, n)
|
||||||
|
local ty = type(t)
|
||||||
|
if ty == "table" then
|
||||||
|
local first_ty = type(next(t))
|
||||||
|
if first_ty == "nil" then
|
||||||
|
-- Assume empty tables are arrays
|
||||||
|
out[n], n = "{}", n + 1
|
||||||
|
return n
|
||||||
|
elseif first_ty == "string" then
|
||||||
|
out[n], n = "{", n + 1
|
||||||
|
local first = true
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
if first then first = false else out[n], n = ",", n + 1 end
|
||||||
|
out[n] = format("\"%s\":", k)
|
||||||
|
n = stringify_impl(v, out, n + 1)
|
||||||
|
end
|
||||||
|
out[n], n = "}", n + 1
|
||||||
|
return n
|
||||||
|
elseif first_ty == "number" then
|
||||||
|
out[n], n = "[", n + 1
|
||||||
|
for i = 1, #t do
|
||||||
|
if i > 1 then out[n], n = ",", n + 1 end
|
||||||
|
n = stringify_impl(t[i], out, n)
|
||||||
|
end
|
||||||
|
out[n], n = "]", n + 1
|
||||||
|
return n
|
||||||
|
else
|
||||||
|
error("Cannot serialize key " .. first_ty)
|
||||||
|
end
|
||||||
|
elseif ty == "string" then
|
||||||
|
if t:match("^[ -~]*$") then
|
||||||
|
out[n], n = gsub(format("%q", t), "\n", "n"), n + 1
|
||||||
|
else
|
||||||
|
out[n], n = "\"" .. gsub(t, ".", escapes) .. "\"", n + 1
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
elseif ty == "number" or ty == "boolean" then
|
||||||
|
out[n],n = tostring(t), n + 1
|
||||||
|
return n
|
||||||
|
else error("Cannot serialize type " .. ty)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stringify(object)
|
||||||
|
local buffer = {}
|
||||||
|
local n = stringify_impl(object, buffer, 1)
|
||||||
|
return concat(buffer, "", 1, n - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function try_parse(msg)
|
||||||
|
local ok, res = pcall(parse, msg)
|
||||||
|
if ok then return res else return nil, res end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
stringify = stringify,
|
||||||
|
try_parse = try_parse,
|
||||||
|
parse = parse,
|
||||||
|
null = null
|
||||||
|
}
|
@ -1,14 +1,22 @@
|
|||||||
|
from typing import *
|
||||||
|
import os.path
|
||||||
|
import uuid
|
||||||
|
|
||||||
from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect
|
from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect
|
||||||
|
from fastapi.responses import PlainTextResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from settings import settings
|
from settings import settings
|
||||||
import auth
|
import auth
|
||||||
from state import StateManager
|
from state import StateManager
|
||||||
|
from proto import Computer, ComputerType
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
state_manager = StateManager()
|
state_manager = StateManager()
|
||||||
|
|
||||||
@app.get("/api/{token}/validate")
|
|
||||||
|
@app.get("/api/{token}/validate", tags=["frontend"])
|
||||||
async def validate_token(token: str):
|
async def validate_token(token: str):
|
||||||
return {"success": auth.validate_frontend(token)}
|
return {"success": auth.validate_frontend(token)}
|
||||||
|
|
||||||
@ -20,7 +28,6 @@ async def state_updates_websocket(websocket: WebSocket, token: str):
|
|||||||
await websocket.close()
|
await websocket.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
await state_manager.on_connect(websocket)
|
await state_manager.on_connect(websocket)
|
||||||
|
|
||||||
@ -30,6 +37,42 @@ async def state_updates_websocket(websocket: WebSocket, token: str):
|
|||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
await state_manager.on_disconnect(websocket)
|
await state_manager.on_disconnect(websocket)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/install", tags=["computer"], response_class=PlainTextResponse)
|
||||||
|
async def serve_install_script():
|
||||||
|
deploy_url = (
|
||||||
|
f'{"https" if settings.deploy_tls else "http"}://{settings.delpoy_path}'
|
||||||
|
)
|
||||||
|
return f"""
|
||||||
|
shell.run("wget {os.path.join(deploy_url, "lua/json.lua")}")
|
||||||
|
shell.run("wget {os.path.join(deploy_url, "lua/framebuffer.lua")}")
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationRequest(BaseModel):
|
||||||
|
type: ComputerType
|
||||||
|
is_advanced: bool
|
||||||
|
label: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationResponse(BaseModel):
|
||||||
|
token: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/computer/register", tags=["computer"], response_model=RegistrationResponse)
|
||||||
|
async def issue_new_token(data: RegistrationRequest):
|
||||||
|
|
||||||
|
computer = Computer(
|
||||||
|
uuid=uuid.uuid4(),
|
||||||
|
group="default",
|
||||||
|
**data.dict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await state_manager.on_computer_register(computer)
|
||||||
|
|
||||||
|
return {"token": auth.encode({"type": "computer", "uuid": str(computer.uuid)})}
|
||||||
|
|
||||||
|
|
||||||
if settings.dev_mode:
|
if settings.dev_mode:
|
||||||
|
|
||||||
print("Starting in development mode.")
|
print("Starting in development mode.")
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
from typing import *
|
from typing import *
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ComputerType(str, Enum):
|
||||||
|
COMPUTER = "computer"
|
||||||
|
TURTLE = "turtle"
|
||||||
|
POCKET = "pocket"
|
||||||
|
|
||||||
|
|
||||||
class Computer(BaseModel):
|
class Computer(BaseModel):
|
||||||
|
type: ComputerType
|
||||||
|
is_advanced: bool
|
||||||
uuid: UUID
|
uuid: UUID
|
||||||
label: str
|
label: Optional[str]
|
||||||
group: str
|
group: str
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,9 @@ class Settings(BaseSettings):
|
|||||||
frontend_path: str = "frontend"
|
frontend_path: str = "frontend"
|
||||||
database_path: str = "db.json"
|
database_path: str = "db.json"
|
||||||
|
|
||||||
|
deploy_tls: bool = False
|
||||||
|
delpoy_path: str = "localhost:8000"
|
||||||
|
|
||||||
secret_key: str
|
secret_key: str
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import asyncio
|
|||||||
from tinydb import TinyDB
|
from tinydb import TinyDB
|
||||||
|
|
||||||
from settings import settings
|
from settings import settings
|
||||||
|
from proto import Computer
|
||||||
|
|
||||||
db = TinyDB(settings.database_path)
|
db = TinyDB(settings.database_path)
|
||||||
computers = db.table("computers")
|
computers = db.table("computers")
|
||||||
@ -19,7 +20,11 @@ class StateManager:
|
|||||||
self.current_state = {"computers": computers.all()}
|
self.current_state = {"computers": computers.all()}
|
||||||
|
|
||||||
async def push_state(self, socket):
|
async def push_state(self, socket):
|
||||||
|
try:
|
||||||
await socket.send_json(self.current_state)
|
await socket.send_json(self.current_state)
|
||||||
|
except RuntimeError:
|
||||||
|
print("dead socket?")
|
||||||
|
self.websockets.remove(socket)
|
||||||
|
|
||||||
async def on_connect(self, socket):
|
async def on_connect(self, socket):
|
||||||
self.websockets.add(socket)
|
self.websockets.add(socket)
|
||||||
@ -30,4 +35,13 @@ class StateManager:
|
|||||||
|
|
||||||
async def on_change(self):
|
async def on_change(self):
|
||||||
self.update_state()
|
self.update_state()
|
||||||
await asyncio.gather(self.push_state(socket) for socket in self.websockets)
|
await asyncio.gather(*[self.push_state(socket) for socket in self.websockets])
|
||||||
|
|
||||||
|
async def on_computer_register(self, computer: Computer):
|
||||||
|
|
||||||
|
# uUiD iS nOt JsOn SeRiAlIzAbLe
|
||||||
|
computer_data = computer.dict()
|
||||||
|
computer_data["uuid"] = str(computer_data["uuid"])
|
||||||
|
|
||||||
|
computers.insert(computer_data)
|
||||||
|
await self.on_change()
|
||||||
|
@ -48,7 +48,7 @@ const Groups: React.FC<{ computers: Array<Computer> }> = ({ computers }) => {
|
|||||||
|
|
||||||
return <>
|
return <>
|
||||||
{
|
{
|
||||||
Array.from(groupMap.entries()).map((entry, index) => {
|
Array.from(groupMap.entries()).map((entry) => {
|
||||||
const [group, computers] = entry;
|
const [group, computers] = entry;
|
||||||
return <div key={group}>
|
return <div key={group}>
|
||||||
<p>{group}:</p>
|
<p>{group}:</p>
|
||||||
@ -63,13 +63,13 @@ const Groups: React.FC<{ computers: Array<Computer> }> = ({ computers }) => {
|
|||||||
const CardList: React.FC<{ computers: Array<Computer> }> = ({ computers }) => {
|
const CardList: React.FC<{ computers: Array<Computer> }> = ({ computers }) => {
|
||||||
return <>
|
return <>
|
||||||
{computers.map(
|
{computers.map(
|
||||||
(computer, index) => <ComputerCard key={computer.uuid} computer={computer} />
|
(computer) => <ComputerCard key={computer.uuid} computer={computer} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComputerCard: React.FC<{ computer: Computer }> = ({ computer }) => {
|
const ComputerCard: React.FC<{ computer: Computer }> = ({ computer }) => {
|
||||||
return <div>
|
return <div>
|
||||||
{computer.label}
|
{computer.is_advanced ? "advanced " : ""}{computer.type} {computer.label || "(no label)"}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
@ -1,9 +1,16 @@
|
|||||||
|
export type ComputerType =
|
||||||
|
"computer" |
|
||||||
|
"turtle" |
|
||||||
|
"pocket"
|
||||||
|
|
||||||
export type Computer = {
|
export type Computer = {
|
||||||
|
type: ComputerType,
|
||||||
|
is_advanced: boolean,
|
||||||
uuid: string,
|
uuid: string,
|
||||||
label: string,
|
label: string | undefined,
|
||||||
group: string,
|
group: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
computers: Array<Computer>
|
computers: Array<Computer>
|
||||||
};
|
}
|
Loading…
Reference in New Issue
Block a user