╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │2 const xit = @import("xit"); │ │3 const rp = xit.repo; │ │4 const web = @import("./web.zig"); │ │5 +const serve_common = @import("./serve_common.zig"); │ │6 const serve_ssh_protocol = @import("./serve_ssh_protocol.zig");│ │7 const serve_ssh = @import("./serve_ssh.zig"); │ │8 const serve_http = @import("./serve_http.zig"); │ ╰──────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │108 try err.print("serving web UI on http://{s}/\n", .{options.wui_listen}); │ │109 try err.flush(); │ │110 │ │110 - web.run(io, allocator, &wui_server, &tasks, admin_repo_path, session_store, err); │ │111 + runWebListener(io, allocator, &wui_server, &tasks, admin_repo_path, session_store, err);│ │112 │ │113 if (@TypeOf(runnable) != void) { │ │114 try runnable.run(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │115 } else { │ │116 try tasks.await(io); │ │117 } │ │118 +} │ │119 + │ │120 +fn runWebListener( │ │121 + io: std.Io, │ │122 + allocator: std.mem.Allocator, │ │123 + net_server: *std.Io.net.Server, │ │124 + tasks: *std.Io.Group, │ │125 + admin_repo_path: []const u8, │ │126 + session_store: web.SessionStore, │ │127 + err: *std.Io.Writer, │ │128 +) void { │ │129 + const Context = struct { │ │130 + io: std.Io, │ │131 + allocator: std.mem.Allocator, │ │132 + admin_repo_path: []const u8, │ │133 + session_store: web.SessionStore, │ │134 + err: *std.Io.Writer, │ │135 + }; │ │136 + │ │137 + const handle = struct { │ │138 + fn h(ctx: Context, stream: std.Io.net.Stream) void { │ │139 + defer stream.close(ctx.io); │ │140 + web.handleConnection(ctx.io, ctx.allocator, stream, ctx.admin_repo_path, ctx.session_store, ctx.err) catch |request_err| {│ │141 + serve_common.logError(ctx.err, "web ui request failed: {s}\n", .{@errorName(request_err)}); │ │142 + }; │ │143 + } │ │144 + }.h; │ │145 + │ │146 + serve_common.runListener(io, net_server, tasks, err, "web ui", Context{ │ │147 + .io = io, │ │148 + .allocator = allocator, │ │149 + .admin_repo_path = admin_repo_path, │ │150 + .session_store = session_store, │ │151 + .err = err, │ │152 + }, handle); │ │153 } │ │154 │ │155 fn parseListenAddress(value: []const u8) !ListenAddress { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/serve_common.zig│ ╰────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │31 │ │32 // accept connections forever, spawning `handleConn(context, stream)` as a task│ │33 // for each. the handler owns the stream (it closes it). `name` labels accept │ │34 -// errors in the log. shared by the http and ssh listeners. │ │34 +// errors in the log. shared by the http, ssh, and web ui listeners. │ │35 pub fn runListener( │ │36 io: std.Io, │ │37 net_server: *std.Io.net.Server, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/web.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │26 .{ .path = "term.ttf", .content_type = "font/ttf", .body = @embedFile("embed/term.ttf") }, │ │27 .{ .path = "haxy.wasm", .content_type = "application/wasm", .body = @embedFile("embed/haxy.wasm") }, │ │28 }; │ │29 - │ │30 -pub fn run( │ │31 - io: std.Io, │ │32 - allocator: std.mem.Allocator, │ │33 - net_server: *std.Io.net.Server, │ │34 - tasks: *std.Io.Group, │ │35 - admin_repo_path: []const u8, │ │36 - session_store: SessionStore, │ │37 - err: *std.Io.Writer, │ │38 -) void { │ │39 - const Listener = struct { │ │40 - io: std.Io, │ │41 - allocator: std.mem.Allocator, │ │42 - net_server: *std.Io.net.Server, │ │43 - tasks: *std.Io.Group, │ │44 - admin_repo_path: []const u8, │ │45 - session_store: SessionStore, │ │46 - err: *std.Io.Writer, │ │47 - │ │48 - fn run(ctx: @This()) void { │ │49 - while (true) { │ │50 - const stream = ctx.net_server.accept(ctx.io) catch |accept_err| { │ │51 - if (accept_err == error.Canceled) return; │ │52 - logError(ctx.err, "web ui accept failed: {s}\n", .{@errorName(accept_err)}); │ │53 - continue; │ │54 - }; │ │55 - │ │56 - const Connection = struct { │ │57 - io: std.Io, │ │58 - allocator: std.mem.Allocator, │ │59 - stream: std.Io.net.Stream, │ │60 - admin_repo_path: []const u8, │ │61 - session_store: SessionStore, │ │62 - err: *std.Io.Writer, │ │63 - │ │64 - fn run(conn: @This()) void { │ │65 - defer conn.stream.close(conn.io); │ │66 - handleConnection(conn.io, conn.allocator, conn.stream, conn.admin_repo_path, conn.session_store, conn.err) catch |request_err| {│ │67 - logError(conn.err, "web ui request failed: {s}\n", .{@errorName(request_err)}); │ │68 - }; │ │69 - } │ │70 - }; │ │71 - │ │72 - ctx.tasks.async(ctx.io, Connection.run, .{Connection{ │ │73 - .io = ctx.io, │ │74 - .allocator = ctx.allocator, │ │75 - .stream = stream, │ │76 - .admin_repo_path = ctx.admin_repo_path, │ │77 - .session_store = ctx.session_store, │ │78 - .err = ctx.err, │ │79 - }}); │ │80 - } │ │81 - } │ │82 - }; │ │83 - │ │84 - tasks.async(io, Listener.run, .{Listener{ │ │85 - .io = io, │ │86 - .allocator = allocator, │ │87 - .net_server = net_server, │ │88 - .tasks = tasks, │ │89 - .admin_repo_path = admin_repo_path, │ │90 - .session_store = session_store, │ │91 - .err = err, │ │92 - }}); │ │93 -} │ │29 │ │95 -fn handleConnection( │ │30 +pub fn handleConnection( │ │31 io: std.Io, │ │32 allocator: std.mem.Allocator, │ │33 stream: std.Io.net.Stream, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │381 return null; │ │382 } │ │383 │ │449 -fn logError(err: *std.Io.Writer, comptime fmt: []const u8, args: anytype) void { │ │450 - err.print(fmt, args) catch return; │ │451 - err.flush() catch {}; │ │452 -} │ │453 - │ │384 // emits the TUI grid cells as static HTML. each web-native Scroll becomes its │ │385 // own absolutely-positioned, natively-scrollable <div> holding its full content,│ │386 // rendered recursively so nested scrolls work. │ ╰──────────────────────────────────────────────────────────────────────────────────────╯