-- 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] = "" .. "\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] = "\n" o[#o + 1] = "\n" end o[#o + 1] = "
addressdirrpcreachablereserve inreserve out
" .. esc(p.pubkey or "") .. "
" .. 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" return table.concat(o) end return M end CES_MANIFEST = { name = "monitor", version = "0.1.0", description = "" } return require("main")