╭ changed ──╮ │src/lib.zig│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │5 pub const serve_ssh = @import("./serve_ssh.zig"); │ │6 pub const serve_ssh_protocol = @import("./serve_ssh_protocol.zig");│ │7 pub const ui = @import("./ui.zig"); │ │8 +pub const web = @import("./web.zig"); │ │9 │ ╰──────────────────────────────────────────────────────────────────────╯ ╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │70 var session_arena = std.heap.ArenaAllocator.init(allocator); │ │71 defer session_arena.deinit(); │ │72 │ │73 + // the seeded admin's event id, so both uis start logged in as them│ │74 + var admin_user_id: [evt.event_id_size]u8 = undefined; │ │75 + │ │76 var session: ui.Session = blk: { │ │77 var arena = std.heap.ArenaAllocator.init(allocator); │ │78 defer arena.deinit(); │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │158 │ │159 var user_ids: [user_data.len][evt.event_id_size]u8 = undefined; │ │160 for (&user_ids) |*id| id.* = evt.EventWithId.randomId(prng.random()); │ │161 + // admin is the first entry of user_data above │ │162 + admin_user_id = user_ids[0]; │ │163 │ │164 var repo_event_ids: [repo_data.len][evt.event_id_size]u8 = undefined; │ │165 for (&repo_event_ids) |*id| id.* = evt.EventWithId.randomId(prng.random());│ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │567 } │ │568 } │ │569 │ │565 - break :blk try ui.Session.init(&session_arena, &repo, .{}); │ │570 + break :blk try ui.Session.init(&session_arena, &repo, .{ .user_id = &admin_user_id });│ │571 }; │ │572 session.is_terminal = true; │ │573 // try uses the default serve options below, so the footer's url points at │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │593 // let the native TUI's page builders open the on-disk repos for the file tree │ │594 session.io = io; │ │595 session.repos_dir = try std.fs.path.join(session_arena.allocator(), &.{ server_path, "repos" });│ │596 + │ │597 + // leave a one-shot session for the first browser to hit the web ui, so it │ │598 + // starts logged in as admin │ │599 + { │ │600 + var data_dir = try cwd.createDirPathOpen(io, server_path, .{}); │ │601 + defer data_dir.close(io); │ │602 + const store = try hx.web.SessionStore.init(io, data_dir); │ │603 + defer store.deinit(); │ │604 + const token = try store.create(&admin_user_id); │ │605 + try store.offerAutoLogin(&token); │ │606 + } │ │607 │ │608 if (cli) { │ │609 var stdout_writer = std.Io.File.stdout().writer(io, &.{}); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │519 return std.fmt.allocPrint(arena.allocator(), repo_segment ++ "{s}", .{identity}); │ │520 } │ │521 │ │522 + // the "owner/name" a repo route carries, null for any other route. the │ │523 + // result borrows the route, so it must outlive the slice. │ │524 + pub fn repoIdentity(self: *const RoutablePage) ?[]const u8 { │ │525 + return switch (self.*) { │ │526 + .repo_files => |*f| f.name.slice(), │ │527 + .repo_commits => |*c| c.name.slice(), │ │528 + .repo_refs => |*r| r.name.slice(), │ │529 + .repo_issues => |*i| i.name.slice(), │ │530 + .repo_settings, .repo_auth => |*name| name.slice(), │ │531 + else => null, │ │532 + }; │ │533 + } │ │534 + │ │535 + // the route this route's page lands on, with no tab or params: the users │ │536 + // list, a user's repos, or a repo's files root. │ │537 + pub fn pageRoot(self: RoutablePage) RoutablePage { │ │538 + return switch (self.parent()) { │ │539 + .home => .default, │ │540 + .user => switch (self) { │ │541 + .user_repos => |u| .{ .user_repos = .{ .name = u.name } }, │ │542 + .user_settings, .user_auth => |name| .{ .user_repos = .{ .name = name } }, │ │543 + else => self, │ │544 + }, │ │545 + // every repo route stores the same identity, so it always fits │ │546 + .repo => repoFilesRoute(self.repoIdentity() orelse return self, null, "", "", 0) orelse self,│ │547 + }; │ │548 + } │ │549 + │ │550 pub fn parent(self: RoutablePage) PageKind { │ │551 return switch (self) { │ │552 .home_users, .home_repos, .home_settings, .home_auth => .home, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │846 // serializable data sent down to web client │ │847 pub const Data = struct { │ │848 user_id: ?[]const u8 = null, │ │849 + // the logged-in user's name, for the views that show it │ │850 + user_name: ?[]const u8 = null, │ │851 // a transient outcome to surface from the last /login POST attempt│ │852 login_failure: ?Home.Auth.Login.Failure = null, │ │853 current_page: RoutablePage = .default, │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │883 .page_arena = arena, │ │884 .haxy_moment = try evt.currentMoment(evt.admin_repo_opts, repo),│ │885 }; │ │856 - try session.loadUserPrefs(); │ │886 + try session.loadUser(); │ │887 return session; │ │888 } │ │889 │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │860 - // load the logged-in user's persisted preferences from the db │ │861 - pub fn loadUserPrefs(self: *Self) !void { │ │890 + // load the logged-in user's name and persisted preferences from the db │ │891 + pub fn loadUser(self: *Self) !void { │ │892 const user_id = self.data.user_id orelse return; │ │893 const moment = self.haxy_moment orelse return; │ │894 if (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, self.arena, user_id)) |user| {│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────╮ │895 + self.data.user_name = user.event.name; │ │896 self.data.enable_ansi = user.event.enable_ansi;│ │897 } │ │898 } │ ╰────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯