Run computers with resources and health checks, add settings for bots, workspaces, computers and users
- Computers get CPUs and memory (default 1 CPU, 2 GB), set at creation and editable; new resources stop, remove and recreate the container, its home dir kept. Every computer container has a docker health check and stays "creating" until it passes; only healthy computers are reached, by hostname, else by their IP on the network (dev outside docker). - Docker is required: the app creates the cobble network on startup and exits without docker. - Production container: deploy/run-app-container.sh mounts .env, the data dir and the docker socket; the database (DB_DIR), media, computers' homes and bun-sqlite's live schema now live on the data volume. - Settings pages for bots, workspaces, computers and users, sharing list components; bots can be deleted; users are created with a password and confirmation, edited and deleted through RBAC-gated APIs. - User types trimmed to super_admin, admin, member, read_only and revoked; admins manage members. UserAvatar shows profile photos from the user row. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import { Monitor } from "lucide-react";
|
||||
|
||||
/**
|
||||
* A computer's avatar in lists: the monitor icon on a small tile.
|
||||
*/
|
||||
export default function ComputerIconTile() {
|
||||
return (
|
||||
<Span className="size-8 shrink-0 flex items-center justify-center rounded-default-sm border border-solid border-hairline bg-paper">
|
||||
<Monitor size={16} aria-hidden />
|
||||
</Span>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import grabMediaURL from "@/src/functions/backend/media/grab-media-url";
|
||||
import type { BUN_SQLITE_COBBLE_USERS_JOIN } from "@/src/types/sql-joins";
|
||||
import userInitials from "@/src/utils/user-initials";
|
||||
import Img from "@moduletrace/twui/layout/Img";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -12,28 +15,43 @@ const UserAvatarSizeClasses = {
|
||||
} as const;
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* Their photo and initials come from this user (`grabUsers`' row, with
|
||||
* `profile_media_id`), unless `src` or `initials` are given.
|
||||
*/
|
||||
person?: BUN_SQLITE_COBBLE_USERS_JOIN | null;
|
||||
/**
|
||||
* A photo to show instead, e.g. one just picked and not saved yet.
|
||||
*/
|
||||
src?: string;
|
||||
/**
|
||||
* Tried when `src` fails to load, e.g. a full image behind a thumbnail.
|
||||
*/
|
||||
img_err_src?: string;
|
||||
initials: string;
|
||||
initials?: string;
|
||||
alt?: string;
|
||||
size?: keyof typeof UserAvatarSizeClasses;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A user's round photo, or their initials on sunken paper when there is none.
|
||||
* A user's round photo (its thumbnail, the full image if that fails), or
|
||||
* their initials on sunken paper when there is none.
|
||||
*/
|
||||
export default function UserAvatar({
|
||||
src,
|
||||
img_err_src,
|
||||
initials,
|
||||
person,
|
||||
src: passed_src,
|
||||
img_err_src: passed_err_src,
|
||||
initials: passed_initials,
|
||||
alt = "",
|
||||
size = "md",
|
||||
className,
|
||||
}: Props) {
|
||||
const photo = person?.profile_media_id ? grabMediaURL({ user: person }) : {};
|
||||
const src = passed_src || photo.media_thumbnail_url || photo.media_url;
|
||||
const img_err_src = passed_src ? passed_err_src : photo.media_url;
|
||||
const initials = passed_initials || userInitials(person || {});
|
||||
|
||||
const base_class = twMerge(
|
||||
"shrink-0 rounded-full bg-sunken",
|
||||
UserAvatarSizeClasses[size],
|
||||
|
||||
Reference in New Issue
Block a user