-- generated by cesdk 0.1.0, do not edit
-- project: monitor 0.1.0
local __mods, __cache = {}, {}
local function require(name)
local c = __cache[name]
if c ~= nil then return c end
local f = __mods[name]
if not f then error("cesdk bundle: module '" .. name .. "' not found", 2) end
local r = f()
if r == nil then r = true end
__cache[name] = r
return r
end
__mods["cesdk.manifest"] = function() return { name = "monitor", version = "0.1.0", protocols = { } } end
__mods["main"] = function()
-- monitor - a public, read-only web dashboard for this CES server. Each HTTP GET
-- renders a fresh snapshot as static HTML: server stats (when ces.server_info is
-- available) plus the peer table. No JavaScript, no CSS, no WebSocket, no polling.
-- All data is public. Refresh to re-query.
local web = require("web")
local snapshot = require("snapshot")
local dash = require("dash")
web.serve{
on_request = function(_req)
return web.html(dash.page(snapshot.gather()))
end,
}
if ces.manifest then
pcall(ces.manifest, { name = "monitor", version = "0.1.0",
description = "public read-only server and peering monitor" })
end
web.run()
end
__mods["web"] = function()
-- web.lua - HTTP/1.1 + WebSocket server machinery over ces.conn.
--
-- Rides the cesweb /i/ proxy. cesweb is the browser's TLS peer, so it terminates
-- the wire protocol and hands a compute program one of two shapes on a ces.conn:
--
-- HTTP : raw request bytes ("GET / HTTP/1.1\r\n...\r\n\r\n"); answer once with
-- an HTTP response, then close (Connection: close).
-- WebSocket : length-framed messages, [u32 BE len][payload]. Frame 0 is the
-- handshake request (so the program can route by path / read cookies);
-- frames 1..N are message payloads. A payload sent back is delivered to
-- the browser as one text WebSocket message.
--
-- The two are told apart by the first byte of the first chunk: an HTTP method is
-- an uppercase ASCII letter (>= 0x41); a frame length's high byte is 0x00
-- (messages are far under 16 MiB). So byte0 == 0 => WebSocket, else => HTTP.
--
-- Usage:
-- local web = require("web")
-- web.serve{
-- on_request = function(req) return web.html("
hi
") end,
-- on_websocket = function(ws)
-- ws.on_message = function(msg) ws.send("you said: "..msg) end
-- end,
-- }
-- web.run() -- = ces.run()
--
-- req = { method, target, path, query, headers = {lowercased->value}, body }
-- ws = { id, path, query, headers, send(msg), close(), on_message, on_close }
local M = {}
-- u32 big-endian pack/unpack via arithmetic (doubles are exact to 2^53).
local function u32be(n)
return string.char(math.floor(n / 16777216) % 256,
math.floor(n / 65536) % 256,
math.floor(n / 256) % 256,
n % 256)
end
local function rd_u32be(s, i) -- i = 1-based index of the length's first byte
local a, b, c, d = s:byte(i, i + 3)
return ((a * 256 + b) * 256 + c) * 256 + d
end
-- Parse an HTTP request (or the WebSocket handshake request in frame 0).
local function parse_request(raw)
local head = raw:match("^(.-)\r\n\r\n")
if not head then return nil end
local body = raw:sub(#head + 5)
local first = head:match("^(.-)\r\n") or head
local method, target = first:match("^(%S+)%s+(%S+)")
if not method then return nil end
local path, query = target:match("^([^?]*)%??(.*)$")
local headers = {}
for line in head:gmatch("\r\n([^\r\n]+)") do
local k, v = line:match("^([^:]+):%s*(.*)$")
if k then headers[k:lower()] = v end
end
return { method = method, target = target, path = path, query = query,
headers = headers, body = body }
end
local REASON = { [200] = "OK", [400] = "Bad Request", [404] = "Not Found",
[500] = "Internal Server Error" }
-- Build an HTTP/1.1 response from a string or a { status, headers, body } table.
local function build_response(resp)
if type(resp) == "string" then resp = { body = resp } end
resp = resp or { status = 404 }
local status = resp.status or 200
local reason = resp.reason or REASON[status] or ""
local body = resp.body or ""
local out = { ("HTTP/1.1 %d %s\r\n"):format(status, reason) }
local have_ct = false
if resp.headers then
for k, v in pairs(resp.headers) do
if k:lower() == "content-type" then have_ct = true end
out[#out + 1] = k .. ": " .. tostring(v) .. "\r\n"
end
end
if not have_ct then out[#out + 1] = "Content-Type: text/html; charset=utf-8\r\n" end
out[#out + 1] = "Content-Length: " .. #body .. "\r\n"
out[#out + 1] = "Connection: close\r\n\r\n"
out[#out + 1] = body
return table.concat(out)
end
-- Response helpers.
function M.html(body, status)
return { status = status or 200, body = body or "",
headers = { ["Content-Type"] = "text/html; charset=utf-8" } }
end
function M.text(body, status)
return { status = status or 200, body = body or "",
headers = { ["Content-Type"] = "text/plain; charset=utf-8" } }
end
function M.response(status, body, headers)
return { status = status, body = body, headers = headers }
end
local function make_ws(conn, hs)
local ws = {
id = conn.id,
path = hs and hs.path or "/",
query = hs and hs.query or "",
headers = hs and hs.headers or {},
on_message = nil,
on_close = nil,
}
function ws.send(msg)
msg = tostring(msg)
return conn:write(u32be(#msg) .. msg)
end
function ws.close() conn:close() end
return ws
end
-- serve{ on_request, on_websocket }: arm the ces.conn listener and dispatch.
function M.serve(opts)
opts = opts or {}
local on_request = opts.on_request
local on_websocket = opts.on_websocket
local conns = {} -- conn.id -> per-connection state
local function handle_http(st, conn)
if not st.buf:find("\r\n\r\n", 1, true) then return end -- headers incomplete
local req = parse_request(st.buf)
if not req then
st.done = true
conn:write(build_response(M.response(400, "bad request")))
conn:close()
return
end
-- Wait for the full request body (POST/PUT/...) before dispatching.
local clen = tonumber(req.headers["content-length"]) or 0
if #req.body < clen then return end
st.done = true
local resp
if on_request then
local ok, r = pcall(on_request, req)
resp = ok and r or M.response(500, "internal error")
end
conn:write(build_response(resp))
conn:close()
end
local function handle_ws(st, conn)
while true do
if #st.buf < 4 then return end
local len = rd_u32be(st.buf, 1)
if #st.buf < 4 + len then return end
local payload = st.buf:sub(5, 4 + len)
st.buf = st.buf:sub(5 + len)
if not st.ws then
local ws = make_ws(conn, parse_request(payload)) -- frame 0 = handshake
st.ws = ws
if on_websocket then pcall(on_websocket, ws) end
elseif st.ws.on_message then
pcall(st.ws.on_message, payload)
end
end
end
ces.conn.set_listener{
on_open = function(conn) conns[conn.id] = { buf = "" } end,
on_data = function(conn, data)
local st = conns[conn.id]
if not st then st = { buf = "" }; conns[conn.id] = st end
if st.done then return end
st.buf = st.buf .. data
if not st.mode then
local b0 = st.buf:byte(1)
if b0 == nil then return end
st.mode = (b0 == 0) and "ws" or "http"
end
if st.mode == "http" then handle_http(st, conn) else handle_ws(st, conn) end
end,
on_close = function(conn)
local st = conns[conn.id]
if st and st.ws and st.ws.on_close then pcall(st.ws.on_close) end
conns[conn.id] = nil
end,
}
end
function M.run() return ces.run() end
return M
end
__mods["snapshot"] = function()
-- snapshot.lua - gather a public, read-only view of this CES server. Every field
-- is already publicly readable: the peer table is an unsigned public read, the
-- server pubkey is the public identity, and ces.server_info (when present) is the
-- host's own published stats. No balances, keys, or privileged peer-control.
local M = {}
local function hex(s)
if not s or s == "" then return "" end
return (s:gsub('.', function(c) return string.format('%02x', c:byte()) end))
end
-- First 8 bytes of a pubkey; the ledger keys accounts by the same prefix.
local function short(s) return hex(s):sub(1, 16) end
function M.gather()
local peers, rows = ces.peers() or {}, {}
local pow_in, pow_out, reachable, outbound, inbound = 0, 0, 0, 0, 0
for _, p in ipairs(peers) do
pow_in = pow_in + (p.inbound_pow or 0)
pow_out = pow_out + (p.outbound_pow or 0)
if p.reachable then reachable = reachable + 1 end
if p.outbound then outbound = outbound + 1 end
if p.inbound then inbound = inbound + 1 end
rows[#rows + 1] = {
pubkey = hex(p.pubkey), addr = p.address or "",
outbound = p.outbound or false, inbound = p.inbound or false,
reachable = p.reachable or false, verified = p.verified or false,
rpc_port = p.rpc_port or 0, pow_in = p.inbound_pow or 0, pow_out = p.outbound_pow or 0,
}
end
local me = ces.owner_pubkey and ces.owner_pubkey() or nil
return {
self = hex(me), self_id = short(me),
server = ces.server_info and ces.server_info() or nil, -- host stats when the intrinsic exists
peers = rows,
metrics = { peers = #peers, outbound = outbound, inbound = inbound,
reachable = reachable, pow_in = pow_in, pow_out = pow_out },
}
end
return M
end
__mods["dash"] = function()
-- dash.lua - the snapshot dashboard page, rendered server-side as static HTML.
-- The server fills every value before sending: no embedded JSON, no script, no
-- author CSS. A plain HTML table renders identically in any browser, including a
-- no-JavaScript client. Refresh re-queries; the page holds no state.
local M = {}
local ENT = { ['&'] = '&', ['<'] = '<', ['>'] = '>' }
local function esc(s) return (tostring(s or ""):gsub('[&<>]', ENT)) end
-- Group a whole number with thousands commas: 1234567 -> "1,234,567".
local function commas(n)
local s = tostring(math.floor(tonumber(n) or 0))
local neg = s:sub(1, 1) == "-"
if neg then s = s:sub(2) end
local out, c = "", 0
for i = #s, 1, -1 do
out = s:sub(i, i) .. out
c = c + 1
if c % 3 == 0 and i > 1 then out = "," .. out end
end
return (neg and "-" or "") .. out
end
local function stat(label, value)
return "| " .. esc(label) .. " | " .. esc(value) .. " |
\n"
end
function M.page(snap)
snap = snap or {}
local S = snap.server or {}
local MET = snap.metrics or {}
local o = {}
o[#o + 1] = "\n"
o[#o + 1] = "CES server monitor\n\n"
o[#o + 1] = "CES server monitor
\n"
o[#o + 1] = "server " .. esc(snap.self or "?") .. ""
if S.version then o[#o + 1] = " · version " .. esc(S.version) end
o[#o + 1] = "
\n"
if S.hello and S.hello ~= "" then o[#o + 1] = "" .. esc(S.hello) .. "
\n" end
o[#o + 1] = "server
\n\n"
if S.circulating ~= nil then
o[#o + 1] = stat("circulating (cr)", commas(math.floor((tonumber(S.circulating) or 0) / 1e8)))
end
if S.accounts ~= nil then o[#o + 1] = stat("accounts", commas(S.accounts)) end
if S.assets ~= nil then o[#o + 1] = stat("assets", commas(S.assets)) end
if S.aliases ~= nil then o[#o + 1] = stat("aliases", commas(S.aliases)) end
if S.tx_count ~= nil then o[#o + 1] = stat("transactions", commas(S.tx_count)) end
if S.tps ~= nil then o[#o + 1] = stat("tps", commas(S.tps)) end
if S.min_difficulty ~= nil then o[#o + 1] = stat("min difficulty", S.min_difficulty) end
if S.fee_tx ~= nil then o[#o + 1] = stat("fee tx", commas(S.fee_tx)) end
if S.fee_query ~= nil then o[#o + 1] = stat("fee query", commas(S.fee_query)) end
o[#o + 1] = stat("peers", commas(MET.peers or 0))
o[#o + 1] = stat("reachable", commas(MET.reachable or 0))
o[#o + 1] = stat("outbound", commas(MET.outbound or 0))
o[#o + 1] = stat("inbound", commas(MET.inbound or 0))
o[#o + 1] = "
\n"
o[#o + 1] = "peers
\n"
local peers = snap.peers or {}
if #peers == 0 then
o[#o + 1] = "no peers
\n"
else
-- Full 64-hex key on its own full-width row, then the data row: keeps the
-- key readable without forcing a very wide column.
o[#o + 1] = "\n"
o[#o + 1] = "| address | dir | rpc | " ..
"reachable | reserve in | reserve out |
\n"
for _, p in ipairs(peers) do
local dir = (p.outbound and p.inbound) and "both"
or (p.outbound and "out") or (p.inbound and "in") or "-"
local addr = (p.addr and p.addr ~= "") and p.addr or "-"
o[#o + 1] = "" .. esc(p.pubkey or "") .. " |
\n"
o[#o + 1] = "| " .. esc(addr) .. " | " .. dir .. " | " ..
esc(p.rpc_port or "-") .. " | " .. (p.reachable and "yes" or "no") ..
" | " .. commas(p.pow_in or 0) .. " | " ..
commas(p.pow_out or 0) .. " |
\n"
end
o[#o + 1] = "
\n"
end
o[#o + 1] = "\n"
return table.concat(o)
end
return M
end
CES_MANIFEST = { name = "monitor", version = "0.1.0", description = "" }
return require("main")