Add bot schedules (routines), open Activity, add Help and a Desktop computer view
Bots create, list, update and delete schedules (cron_jobs, Bun.cron in UTC); a per-minute cron queues due runs as tasks. Routines, Activity (super admins) and Help get their own rail aside. Help covers familiar problems, starting with signing in to Google through Cookie-Editor over the new Desktop (noVNC) view. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,12 +1,6 @@
|
||||
import {
|
||||
Activity,
|
||||
CircleHelp,
|
||||
Clock,
|
||||
LogOut,
|
||||
SlidersHorizontal,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import { LogOut, SlidersHorizontal, type LucideIcon } from "lucide-react";
|
||||
import { SettingsDefaultPath } from "@/src/dict/settings-pages-dict";
|
||||
import { RailPages } from "@/src/dict/rail-pages-dict";
|
||||
|
||||
export type RailUserMenuItem = {
|
||||
title: string;
|
||||
@@ -20,16 +14,19 @@ export type RailUserMenuItem = {
|
||||
* Rendered last, after a rule.
|
||||
*/
|
||||
destructive?: boolean;
|
||||
/**
|
||||
* Shown to super admins only.
|
||||
*/
|
||||
super_admin_only?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exactly the five popover items. Nothing else lives in the rail: these
|
||||
* destinations are only reachable from here.
|
||||
* The popover items: the rail pages (routines, activity, help), settings,
|
||||
* sign out. Nothing else lives in the rail: these destinations are only
|
||||
* reachable from here.
|
||||
*/
|
||||
export const RailUserMenuItems: RailUserMenuItem[] = [
|
||||
{ title: "Activity", Icon: Activity },
|
||||
{ title: "Routines", Icon: Clock },
|
||||
...RailPages,
|
||||
{ title: "Settings", url: SettingsDefaultPath, Icon: SlidersHorizontal },
|
||||
{ title: "Help", Icon: CircleHelp },
|
||||
{ title: "Sign out", url: "/auth/logout", Icon: LogOut, destructive: true },
|
||||
];
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useContext } from "react";
|
||||
import Divider from "@moduletrace/twui/layout/Divider";
|
||||
import { RailPages } from "@/src/dict/rail-pages-dict";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import isPassThroughUser from "@/src/utils/rbac/is-pass-through-user";
|
||||
import RailBackLink from "./rail-back-link";
|
||||
import RailSettingsLink from "./rail-settings-link";
|
||||
|
||||
type Props = {
|
||||
pathname?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The rail on routines, activity and help: back to the bots, then those
|
||||
* pages (super admin ones for super admins only).
|
||||
*/
|
||||
export default function RailPagesList({ pathname }: Props) {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const current = pathname?.replace(/\/$/, "");
|
||||
const is_super_admin = isPassThroughUser({ user_types: pageProps?.user_types });
|
||||
|
||||
return (
|
||||
<>
|
||||
<RailBackLink />
|
||||
<Divider className="shrink-0 mx-1 lg:mx-3 my-2 w-auto border-hairline dark:border-hairline" />
|
||||
{RailPages.filter((page) => is_super_admin || !page.super_admin_only).map((page) => (
|
||||
<RailSettingsLink
|
||||
key={page.url}
|
||||
{...page}
|
||||
selected={current == page.url || Boolean(current?.startsWith(`${page.url}/`))}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
import { useContext } from "react";
|
||||
import Paper from "@moduletrace/twui/elements/Paper";
|
||||
import Divider from "@moduletrace/twui/layout/Divider";
|
||||
import { RailUserMenuItems } from "../(dict)/rail-user-menu-dict";
|
||||
import RailUserMenuEntry from "./rail-user-menu-entry";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import isPassThroughUser from "@/src/utils/rbac/is-pass-through-user";
|
||||
|
||||
/**
|
||||
* 220px popover: the only element in the shell with elevation.
|
||||
* 220px popover: the only element in the shell with elevation. Super
|
||||
* admin items show to super admins only.
|
||||
*/
|
||||
export default function RailUserMenu() {
|
||||
const items = RailUserMenuItems.filter((item) => !item.destructive);
|
||||
const destructive = RailUserMenuItems.filter((item) => item.destructive);
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const is_super_admin = isPassThroughUser({ user_types: pageProps?.user_types });
|
||||
const shown = RailUserMenuItems.filter((item) => is_super_admin || !item.super_admin_only);
|
||||
const items = shown.filter((item) => !item.destructive);
|
||||
const destructive = shown.filter((item) => item.destructive);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
|
||||
@@ -3,7 +3,9 @@ import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import useLiveBots from "@/src/hooks/use-live-bots";
|
||||
import useLiveWorkspaces from "../(hooks)/use-live-workspaces";
|
||||
import { RailPages } from "@/src/dict/rail-pages-dict";
|
||||
import RailHeader from "../(partials)/rail-header";
|
||||
import RailPagesList from "../(partials)/rail-pages-list";
|
||||
import RailCrewList from "../(partials)/rail-crew-list";
|
||||
import RailSettingsList from "../(partials)/rail-settings-list";
|
||||
import RailUserRow from "../(partials)/rail-user-row";
|
||||
@@ -13,13 +15,17 @@ import RailWorkspaceList from "../(partials)/rail-workspace-list";
|
||||
* Left rail: wordmark and Add, then the crew (pinned coordinator first,
|
||||
* the latest bots, the workspaces). Inside a workspace it lists that
|
||||
* workspace's crew instead; on `/admin/settings/*` a back link and the
|
||||
* settings sub pages. Collapses to a 64px icon rail below 1024px.
|
||||
* settings sub pages, and on routines, activity and help a back link and
|
||||
* those pages (`RailPages`). Collapses to a 64px icon rail below 1024px.
|
||||
*/
|
||||
export default function BotsRail() {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
|
||||
const pathname = pageProps?.url?.pathname;
|
||||
const is_settings_page = Boolean(pathname?.startsWith("/admin/settings"));
|
||||
const is_rail_page = RailPages.some(
|
||||
(page) => pathname == page.url || Boolean(pathname?.startsWith(`${page.url}/`)),
|
||||
);
|
||||
const workspace = pageProps?.workspace;
|
||||
const in_workspace = Boolean(workspace?.id && pathname?.startsWith("/admin/workspaces/"));
|
||||
|
||||
@@ -39,10 +45,12 @@ export default function BotsRail() {
|
||||
className="w-16 lg:w-[260px] shrink-0 h-full gap-0! items-stretch border-0 border-r border-solid border-hairline"
|
||||
aria-label={is_settings_page ? "Settings" : in_workspace ? workspace?.name || "Workspace" : "Bots"}
|
||||
>
|
||||
<RailHeader show_add={!is_settings_page} />
|
||||
<RailHeader show_add={!is_settings_page && !is_rail_page} />
|
||||
<Stack className="grow min-h-0 overflow-y-auto gap-0! items-stretch px-2 lg:px-3 pt-1 pb-3">
|
||||
{is_settings_page ? (
|
||||
<RailSettingsList pathname={pathname} />
|
||||
) : is_rail_page ? (
|
||||
<RailPagesList pathname={pathname} />
|
||||
) : in_workspace && live_workspace ? (
|
||||
<RailWorkspaceList
|
||||
workspace={live_workspace}
|
||||
|
||||
Reference in New Issue
Block a user