Add computer settings pages and restore workspace pages

This commit is contained in:
2026-10-19 13:30:02 +01:00
parent 7667f2bc2f
commit 7b1ccfeb62
36 changed files with 1082 additions and 131 deletions
@@ -14,7 +14,10 @@ type Props = {
* Edits this computer; creates one when not given.
*/
computer?: ComputerWithStatus;
onCancel: () => void;
/**
* Left out when the form is embedded in a settings page.
*/
onCancel?: () => void;
/**
* Gets the new computer (still being set up) and the server's note.
*/
+4 -1
View File
@@ -122,7 +122,10 @@ export default function AddFlow({ children }: Props) {
const pathname = pageProps?.url?.pathname;
if (
(linked && pathname == `/admin/settings/workspaces/${workspace?.id}`) ||
(linked &&
(pathname == `/admin/workspaces/${workspace?.id}` ||
pathname ==
`/admin/settings/workspaces/${workspace?.id}`)) ||
pathname == SettingsComputersPath
) {
window.location.reload();
@@ -49,7 +49,7 @@ export default function AddWorkspaceForm({ onCancel }: Props) {
await setProfileImage({ target: "workspace", id: workspace_id, data_url: image.data_url }).catch(() => null);
}
window.location.href = `/admin/settings/workspaces/${workspace_id}`;
window.location.href = `/admin/workspaces/${workspace_id}`;
};
return (
@@ -1,6 +1,7 @@
import { useContext, type ReactNode } from "react";
import Divider from "@moduletrace/twui/layout/Divider";
import BotAvatar from "@/src/components/general/bot-avatar";
import ComputerIconTile from "@/src/components/general/computer-icon-tile";
import UserAvatar from "@/src/components/general/user-avatar";
import WorkspaceAvatar from "@/src/components/general/workspace-avatar";
import { UserTypes } from "@/src/dict/user-types-dict";
@@ -10,8 +11,11 @@ import {
SettingsRecordSections,
} from "@/src/dict/settings-record-sections-dict";
import { AppContext } from "@/src/pages/__root";
import computerResourcesLabel from "@/src/utils/computer-resources-label";
import computerStatusLabel from "@/src/utils/computer-status-label";
import parseSettingsRecordPath from "@/src/utils/parse-settings-record-path";
import parseUserTypes from "@/src/utils/parse-user-types";
import isPassThroughUser from "@/src/utils/rbac/is-pass-through-user";
import RailBackLink from "./rail-back-link";
import RailSettingsLink from "./rail-settings-link";
import RailSettingsList from "./rail-settings-list";
@@ -35,6 +39,9 @@ export default function RailSettingsAside({ pathname }: Props) {
}
const back = SettingsRecordBack[record.kind];
const is_super_admin = isPassThroughUser({
user_types: pageProps?.user_types,
});
const sections = SettingsRecordSections[record.kind].filter((section) => {
if (
record.kind == "bots" &&
@@ -44,6 +51,14 @@ export default function RailSettingsAside({ pathname }: Props) {
return false;
}
if (
record.kind == "computers" &&
!is_super_admin &&
(section.url == "#details" || section.url == "#danger")
) {
return false;
}
if (section.url == "#models") {
return record.kind == "bots"
? Boolean(pageProps?.bot_harness_overview?.harnesses?.length)
@@ -89,7 +104,7 @@ export default function RailSettingsAside({ pathname }: Props) {
avatar = (
<WorkspaceAvatar image_media_id={workspace.image_media_id} />
);
} else {
} else if (record.kind == "users") {
const person = pageProps?.target_person;
if (!person?.id) return <RailSettingsList pathname={pathname} />;
@@ -108,6 +123,17 @@ export default function RailSettingsAside({ pathname }: Props) {
`User ${person.id}`;
subtitle = types || person.email || "User";
avatar = <UserAvatar person={person} size="md" />;
} else {
const computer = pageProps?.computer;
if (!computer?.id) return <RailSettingsList pathname={pathname} />;
title = computer.name || `Computer ${computer.id}`;
subtitle = [
computerStatusLabel({ computer }),
computerResourcesLabel({ computer }),
].join(" · ");
avatar = <ComputerIconTile />;
}
return (
@@ -1,12 +1,9 @@
import { useContext } from "react";
import Link from "@moduletrace/twui/layout/Link";
import Span from "@moduletrace/twui/layout/Span";
import Stack from "@moduletrace/twui/layout/Stack";
import WorkspaceAvatar from "@/src/components/general/workspace-avatar";
import { twMerge } from "tailwind-merge";
import { AppContext } from "@/src/pages/__root";
import type { WorkspaceWithActivity } from "@/src/types";
import canManageCrew from "@/src/utils/rbac/can-manage-crew";
type Props = {
workspace: WorkspaceWithActivity;
@@ -18,14 +15,9 @@ type Props = {
* hue, so unread is said in words beside the description.
*/
export default function WorkspaceRow({ workspace, selected }: Props) {
const { pageProps } = useContext(AppContext);
const href = canManageCrew({ user_types: pageProps?.user_types })
? `/admin/settings/workspaces/${workspace.id}`
: `/admin/workspaces/${workspace.id}`;
return (
<Link
href={href}
href={`/admin/workspaces/${workspace.id}`}
aria-current={selected ? "page" : undefined}
title={workspace.name}
className={twMerge(