╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────╮ │src/serve_ssh.zig│ ╰─────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │26 admin_repo_path: []const u8, │ │27 repo_root_path: []const u8, │ │28 wui_port: u16, // port the web UI is served on, shown in the TUI footer's url │ │29 - clone_http_port: u16, │ │30 - clone_ssh_port: u16, │ │29 + clone_http_port: ?u16, │ │30 + clone_ssh_port: ?u16, │ │31 err: *std.Io.Writer, │ │32 │ │33 pub fn handleSession(self: *const SessionHandler, sess: *ssh.SessionCtx, request: ssh.Request) !void {│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │60 const cwd_path = try std.process.currentPathAlloc(io, allocator); │ │61 defer allocator.free(cwd_path); │ │62 │ │63 - const work_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server", "admin" });│ │63 + var cli = false; │ │64 + var serve_args: std.ArrayList([]const u8) = .empty; │ │65 + defer serve_args.deinit(allocator); │ │66 + var arg_it = try init.minimal.args.iterateAllocator(allocator); │ │67 + defer arg_it.deinit(); │ │68 + _ = arg_it.skip(); │ │69 + while (arg_it.next()) |arg| { │ │70 + if (std.mem.eql(u8, "--cli", arg)) { │ │71 + cli = true; │ │72 + } else { │ │73 + try serve_args.append(allocator, arg); │ │74 + } │ │75 + } │ │76 + var serve_options = try hx.command.parseServeOptions(allocator, serve_args.items); │ │77 + │ │78 + const server_path = if (std.mem.eql(u8, serve_options.data_dir, ".")) │ │79 + try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" }) │ │80 + else │ │81 + try std.fs.path.resolve(allocator, &.{ cwd_path, serve_options.data_dir }); │ │82 + defer allocator.free(server_path); │ │83 + serve_options.data_dir = server_path; │ │84 + │ │85 + const work_path = try std.fs.path.join(allocator, &.{ server_path, "admin" }); │ │86 defer allocator.free(work_path); │ │87 │ │88 const Repo = rp.Repo(.xit, evt.admin_repo_opts); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │742 │ │743 for (repo_event_ids) |id_bytes| { │ │744 const repo_id = std.fmt.bytesToHex(id_bytes, .lower); │ │723 - const repo_path = try std.fs.path.join(arena.allocator(), &.{ cwd_path, temp_dir_name, "server", "repos", &repo_id });│ │745 + const repo_path = try std.fs.path.join(arena.allocator(), &.{ server_path, "repos", &repo_id }); │ │746 var dest_dir = try cwd.createDirPathOpen(io, repo_path, .{}); │ │747 defer dest_dir.close(io); │ │748 try copyDir(io, template_dir, dest_dir); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │755 │ │756 // start the server │ │757 │ │736 - var cli = false; │ │737 - │ │738 - var arg_it = try init.minimal.args.iterateAllocator(allocator); │ │739 - defer arg_it.deinit(); │ │740 - _ = arg_it.skip(); │ │741 - while (arg_it.next()) |arg| { │ │742 - if (std.mem.eql(u8, "--cli", arg)) { │ │743 - cli = true; │ │744 - } │ │745 - } │ │746 - │ │747 - const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" }); │ │748 - defer allocator.free(server_path); │ │749 - │ │758 // let the native TUI's page builders open the on-disk repos for the file tree │ │759 session.io = io; │ │760 session.repos_dir = try std.fs.path.join(session_arena.allocator(), &.{ server_path, "repos" });│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │782 io: std.Io, │ │783 key_path: []const u8, │ │784 │ │777 - pub fn run(self: @This(), web_port: u16, http_port: u16, ssh_port: u16) !void { │ │785 + pub fn run(self: @This(), web_port: u16, http_port: ?u16, ssh_port_maybe: ?u16) !void {│ │786 _ = web_port; │ │787 _ = http_port; │ │788 + const ssh_port = ssh_port_maybe orelse return error.SshDisabled; │ │789 std.debug.print( │ │790 \\ │ │791 \\connect to the TUI with: │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │812 } │ │813 }; │ │814 │ │806 - try srv.run(.xit, .{}, io, allocator, cwd_path, .{ │ │807 - .data_dir = server_path, │ │808 - }, run_opts.err, Runnable{ .io = io, .key_path = key_path }); │ │815 + try srv.run(.xit, .{}, io, allocator, cwd_path, serve_options, run_opts.err, Runnable{ .io = io, .key_path = key_path });│ │816 } else { │ │817 var null_writer = std.Io.Writer.Discarding.init(&.{}); │ │818 const run_opts = hx.main.RunOpts{ .out = &null_writer.writer, .err = &null_writer.writer }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │823 session: *ui.Session, │ │824 repo: *Repo, │ │825 │ │819 - pub fn run(self: @This(), web_port: u16, http_port: u16, ssh_port: u16) !void { │ │826 + pub fn run(self: @This(), web_port: u16, http_port: ?u16, ssh_port: ?u16) !void {│ │827 // launch the TUI; the footer points at whatever port was bound │ │828 self.session.web_port = web_port; │ │829 self.session.data.clone_http_port = http_port; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │832 } │ │833 }; │ │834 │ │828 - try srv.run(.xit, .{}, io, allocator, cwd_path, .{ │ │829 - .data_dir = server_path, │ │830 - }, run_opts.err, Runnable{ .io = io, .allocator = allocator, .session = &session, .repo = &repo }); │ │835 + try srv.run(.xit, .{}, io, allocator, cwd_path, serve_options, run_opts.err, Runnable{ .io = io, .allocator = allocator, .session = &session, .repo = &repo });│ │836 } │ │837 } │ │838 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/ui/CloneUrl.zig│ ╰───────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │12 pub const View = struct { │ │13 box: wgt.Box(ui.Widget), │ │14 session: *ui.Session, │ │15 - urls: [2][]const u8, │ │15 + protocols: [2]Protocol, │ │16 + protocol_count: usize, │ │17 selected: usize = 0, │ │18 │ │18 - const http_index: usize = 0; │ │19 - const ssh_index: usize = 1; │ │20 - const input_index: usize = 2; │ │19 + const Protocol = struct { │ │20 + name: []const u8, │ │21 + url: []const u8, │ │22 + }; │ │23 │ │24 pub fn init(allocator: std.mem.Allocator, session: *ui.Session, identity: []const u8) !View { │ │23 - const http_port = session.data.clone_http_port orelse return error.MissingClonePort; │ │24 - const ssh_port = session.data.clone_ssh_port orelse return error.MissingClonePort; │ │25 const aa = session.page_arena.allocator(); │ │26 - const urls = [2][]const u8{ │ │27 - try std.fmt.allocPrint(aa, "http://localhost:{d}/repo/{s}", .{ http_port, identity }), │ │28 - try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}", .{ ssh_port, identity }), │ │29 - }; │ │26 + var protocols: [2]Protocol = undefined; │ │27 + var protocol_count: usize = 0; │ │28 + if (session.data.clone_http_port) |port| { │ │29 + protocols[protocol_count] = .{ │ │30 + .name = "http", │ │31 + .url = try std.fmt.allocPrint(aa, "http://localhost:{d}/repo/{s}", .{ port, identity }), │ │32 + }; │ │33 + protocol_count += 1; │ │34 + } │ │35 + if (session.data.clone_ssh_port) |port| { │ │36 + protocols[protocol_count] = .{ │ │37 + .name = "ssh", │ │38 + .url = try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}", .{ port, identity }), │ │39 + }; │ │40 + protocol_count += 1; │ │41 + } │ │42 + if (protocol_count == 0) return error.MissingClonePort; │ │43 + │ │44 + var max_url_len: usize = 0; │ │45 + for (protocols[0..protocol_count]) |protocol| max_url_len = @max(max_url_len, protocol.url.len); │ │46 │ │47 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz });│ │48 errdefer box.deinit(allocator); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │49 box.getFocus().kind = .{ .custom = "form:" }; │ │50 │ │35 - inline for ([_][]const u8{ "http", "ssh" }) |protocol| { │ │36 - var label = try wgt.TextBox(ui.Widget).init(allocator, protocol, .{ .border_style = .hidden, .wrap_kind = .none }); │ │51 + for (protocols[0..protocol_count]) |protocol| { │ │52 + var label = try wgt.TextBox(ui.Widget).init(allocator, protocol.name, .{ .border_style = .hidden, .wrap_kind = .none }); │ │53 errdefer label.deinit(allocator); │ │54 label.getFocus().focusable = true; │ │39 - try box.children.put(allocator, label.getFocus().id, .{ .widget = .{ .text_box = label }, .rect = null, .min_size = .{ .width = protocol.len + 2, .height = 3 } }); │ │55 + try box.children.put(allocator, label.getFocus().id, .{ .widget = .{ .text_box = label }, .rect = null, .min_size = .{ .width = protocol.name.len + 2, .height = 3 } });│ │56 } │ │57 │ │58 var url_input = try wgt.TextInput(ui.Widget).init(allocator, .{ │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯