Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/compute/menu.tsx
Views: 687
/*1Compute server hamburger menu.2*/34import type { MenuProps } from "antd";5import { Button, Dropdown, Spin } from "antd";6import { useMemo, useState } from "react";78import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";9import { A, Icon } from "@cocalc/frontend/components";10import ShowError from "@cocalc/frontend/components/error";11import {12setServerConfiguration,13setTemplate,14} from "@cocalc/frontend/compute/api";15import openSupportTab from "@cocalc/frontend/support/open";16import CloneModal from "./clone";17import { EditModal } from "./compute-server";18import { LogModal } from "./compute-server-log";19import getTitle from "./get-title";20import { AppLauncherModal } from "./launcher";21import { SerialLogModal } from "./serial-port-output";22import { TitleColorModal } from "./title-color";23import { AutomaticShutdownModal } from "./automatic-shutdown";2425function getServer({ id, project_id }) {26return redux27.getProjectStore(project_id)28.getIn(["compute_servers", `${id}`])29?.toJS();30}3132export function getApps(image) {33const IMAGES = redux.getStore("customize").get("compute_servers_images");34if (IMAGES == null || typeof IMAGES == "string") {35// string when error36return {};37}38let apps =39IMAGES.getIn([image, "apps"])?.toJS() ??40IMAGES.getIn(["defaults", "apps"])?.toJS() ??41{};42if (IMAGES.getIn([image, "jupyterKernels"]) === false) {43apps = { ...apps, jupyterlab: undefined };44}45if (apps["xpra"]) {46if (!apps["xpra"].tip) {47apps["xpra"].tip =48"Launch an X11 Linux Graphical Desktop environment running directly on the compute server.";49}50}51return apps;52}5354function getItems({55id,56project_id,57account_id,58isAdmin,59}: {60id: number;61project_id: string;62account_id: string;63title?: string;64color?: string;65isAdmin?: boolean;66}): MenuProps["items"] {67if (!id) {68return [];69}70const server = getServer({ id, project_id });71if (server == null) {72return [73{74key: "loading",75label: (76<>77Loading... <Spin />78</>79),80disabled: true,81},82];83}84const apps = getApps(server.configuration?.image ?? "defaults");85const is_owner = account_id == server.account_id;8687// will be used for start/stop/etc.88// const is_collab = is_owner || server.configuration?.allowCollaboratorControl;8990const titleAndColor = {91key: "title-color",92icon: <Icon name="colors" />,93disabled: !is_owner,94label: "Edit Title and Color",95};96const automaticShutdown = {97key: "automatic-shutdown",98icon: <Icon name="stopwatch" />,99disabled: server.cloud == "onprem",100label: "Edit Automatic Shutdown",101};102const jupyterlab = {103key: "top-jupyterlab",104label: "JupyterLab",105icon: <Icon name="jupyter" />,106disabled:107apps["jupyterlab"] == null ||108server.state != "running" ||109!server.data?.externalIp,110};111const vscode = {112key: "top-vscode",113label: "VS Code",114icon: <Icon name="vscode" />,115disabled:116apps["vscode"] == null ||117server.state != "running" ||118!server.data?.externalIp,119};120const xpra = {121key: "xpra",122label: "X11 Desktop",123icon: <Icon name="desktop" />,124disabled:125apps["xpra"] == null ||126server.state != "running" ||127!server.data?.externalIp,128};129130const optionItems: (131| { key: string; label; icon; disabled?: boolean }132| { type: "divider" }133)[] = [134// {135// key: "dns",136// label: "DNS...",137// icon: <Icon name="network" />,138// },139{140key: "ephemeral",141label: "Ephemeral",142icon: (143<Icon144style={{ fontSize: "12pt" }}145name={server.configuration?.ephemeral ? "check-square" : "square"}146/>147),148},149{150key: "allowCollaboratorControl",151label: "Collaborator Control",152icon: (153<Icon154style={{ fontSize: "12pt" }}155name={156server.configuration?.allowCollaboratorControl157? "check-square"158: "square"159}160/>161),162},163{164type: "divider",165},166];167if (server.cloud == "google-cloud") {168optionItems.push({169key: "autoRestart",170label: "Automatically Restart",171disabled: server.cloud != "google-cloud",172icon: (173<Icon174style={{ fontSize: "12pt" }}175name={server.configuration?.autoRestart ? "check-square" : "square"}176/>177),178});179optionItems.push({180key: "enableNestedVirtualization",181label: "Nested Virtualization",182disabled:183server.cloud != "google-cloud" || server.state != "deprovisioned",184icon: (185<Icon186style={{ fontSize: "12pt" }}187name={188server.configuration?.enableNestedVirtualization189? "check-square"190: "square"191}192/>193),194});195}196if (isAdmin) {197if (optionItems[optionItems.length - 1]?.["type"] != "divider") {198optionItems.push({199type: "divider",200});201}202optionItems.push({203key: "template",204label: "Use as Template",205icon: (206<Icon207style={{ fontSize: "12pt" }}208name={server.template?.enabled ? "check-square" : "square"}209/>210),211});212}213214const options = {215key: "options",216label: "Options",217disabled: !is_owner,218icon: <Icon name="gears" />,219children: [220{221key: "run-app-on",222type: "group",223label: "Configure Server",224children: optionItems,225},226],227};228229const help = {230key: "help",231icon: <Icon name="question-circle" />,232label: "Help",233children: [234{235key: "documentation",236icon: <Icon name="question-circle" />,237label: (238<A href="https://doc.cocalc.com/compute_server.html">Documentation</A>239),240},241{242key: "support",243icon: <Icon name="medkit" />,244label: "Support",245},246{247key: "videos",248icon: <Icon name="youtube" style={{ color: "red" }} />,249label: (250<A href="https://www.youtube.com/playlist?list=PLOEk1mo1p5tJmEuAlou4JIWZFH7IVE2PZ">251Videos252</A>253),254},255{256type: "divider",257},258{259key: "dedicated",260icon: <Icon name="bank" />,261label: "Dedicated Always On Server for 6+ Months...",262},263],264};265266const settings = {267key: "settings",268icon: <Icon name="settings" />,269label: is_owner ? "Settings" : "Details...",270};271272const clone = {273key: "clone",274icon: <Icon name="copy" />,275label: "Clone Server Configuration",276};277278return [279titleAndColor,280// {281// type: "divider",282// },283// {284// key: "new-jupyter",285// label: "New Jupyter Notebook",286// icon: <Icon name="jupyter" />,287// disabled: server.state != "running",288// },289// {290// key: "new-terminal",291// label: "New Linux Terminal",292// icon: <Icon name="terminal" />,293// disabled: server.state != "running",294// },295{296type: "divider",297},298jupyterlab,299vscode,300xpra,301{302type: "divider",303},304settings,305options,306clone,307automaticShutdown,308{309type: "divider",310},311{312key: "control-log",313icon: <Icon name="history" />,314label: "Configuration Log",315},316{317key: "serial-console-log",318disabled:319server.cloud != "google-cloud" ||320server.state == "off" ||321server.state == "deprovisioned",322icon: <Icon name="laptop" />,323label: "Serial Console",324},325{326type: "divider",327},328help,329// {330// key: "control",331// icon: <Icon name="wrench" />,332// label: "Control",333// children: [334// {335// key: "start",336// icon: <Icon name="play" />,337// label: "Start",338// },339// {340// key: "suspend",341// icon: <Icon name="pause" />,342// label: "Suspend",343// },344// {345// key: "stop",346// icon: <Icon name="stop" />,347// label: "Stop",348// },349// {350// key: "reboot",351// icon: <Icon name="redo" />,352// label: "Hard Reboot",353// danger: true,354// },355// {356// key: "deprovision",357// icon: <Icon name="trash" />,358// label: "Deprovision",359// danger: true,360// },361// {362// key: "delete",363// icon: <Icon name="trash" />,364// label: "Delete",365// danger: true,366// },367// ],368// },369// {370// key: "files",371// label: "Files",372// icon: <Icon name="files" />,373// children: [374// {375// key: "explorer",376// label: "Explorer",377// icon: <Icon name="folder-open" />,378// },379// {380// type: "divider",381// },382// {383// key: "sync",384// icon: <Icon name="sync" />,385// label: "Sync Files",386// },387// {388// key: "disk",389// icon: <Icon name="disk-drive" />,390// label: "Disk Space",391// },392// {393// type: "divider",394// },395// { key: "file1", label: "foo.ipynb", icon: <Icon name="jupyter" /> },396// { key: "file2", label: "tmp/a.term", icon: <Icon name="terminal" /> },397// {398// key: "file3",399// label: "compoute-server-38/foo-bar.ipynb",400// icon: <Icon name="jupyter" />,401// },402// {403// key: "file4",404// label: "compoute-server-38/example.ipynb",405// icon: <Icon name="jupyter" />,406// },407// ],408// },409];410}411412export default function Menu({413id,414project_id,415style,416fontSize,417size,418}: {419id: number;420project_id: string;421style?;422fontSize?;423size?;424}) {425const [error, setError] = useState<string>("");426const [open, setOpen] = useState<boolean>(false);427const account_id = useTypedRedux("account", "account_id");428const [modal, setModal] = useState<any>(null);429const close = () => setModal(null);430const [title, setTitle] = useState<{431title: string;432color: string;433project_specific_id: number;434} | null>(null);435const isAdmin = useTypedRedux("account", "is_admin");436const { items, onClick } = useMemo(() => {437if (!open) {438return { onClick: () => {}, items: [] };439}440441(async () => {442setTitle(await getTitle(id));443})();444return {445items: getItems({ id, project_id, account_id, isAdmin }),446onClick: async (obj) => {447setOpen(false);448let cmd = obj.key.startsWith("top-") ? obj.key.slice(4) : obj.key;449switch (cmd) {450case "control-log":451setModal(<LogModal id={id} close={close} />);452break;453454case "settings":455setModal(456<EditModal id={id} project_id={project_id} close={close} />,457);458break;459460case "clone":461setModal(462<CloneModal id={id} project_id={project_id} close={close} />,463);464break;465466case "serial-console-log":467setModal(468<SerialLogModal469id={id}470title={title?.title ?? ""}471close={close}472/>,473);474break;475476case "vscode":477case "jupyterlab":478case "xpra":479setModal(480<AppLauncherModal481name={cmd}482id={id}483project_id={project_id}484close={close}485/>,486);487break;488489case "title-color":490setModal(491<TitleColorModal id={id} project_id={project_id} close={close} />,492);493break;494495case "automatic-shutdown":496setModal(497<AutomaticShutdownModal498id={id}499project_id={project_id}500close={close}501/>,502);503break;504505case "ephemeral":506case "allowCollaboratorControl":507case "autoRestart":508case "enableNestedVirtualization":509case "template":510const server = getServer({ id, project_id });511if (server != null) {512try {513if (obj.key == "template") {514await setTemplate({515id,516template: { enabled: !server.template?.enabled },517});518} else {519await setServerConfiguration({520id,521configuration: {522[cmd]: !server.configuration?.[cmd],523},524});525}526} catch (err) {527setError(`${err}`);528}529}530break;531532case "documentation":533case "videos":534// click opens new tab anyways535break;536537case "support":538openSupportTab({539type: "question",540subject: `Compute Server (Global Id: ${id}; Project Specific Id: ${title?.project_specific_id})`,541body: `I am using a compute server, and have a question...`,542});543break;544545case "dedicated":546openSupportTab({547type: "question",548subject: `Compute Server (Global Id: ${id}; Project Specific Id: ${title?.project_specific_id})`,549body: `I need a dedicated always on compute server for at least 6 months, and am interested in significant discounts.\nI would love to tell you about my problem, and see if CoCalc can help!`,550});551break;552553default:554setError(`not implemented -- '${cmd}'`);555}556},557};558}, [id, project_id, open, title]);559560return (561<div style={style}>562<Dropdown563menu={{ items, onClick }}564trigger={["click"]}565onOpenChange={setOpen}566>567<Button type="text" size={size}>568<Icon569name="ellipsis"570style={{ fontSize: fontSize ?? "15pt", color: "#000" }}571rotate="90"572/>573</Button>574</Dropdown>575{modal}576<ShowError577error={error}578setError={setError}579style={{580fontWeight: "normal",581whiteSpace: "normal",582position: "absolute",583right: 0,584maxWidth: "500px",585zIndex: 1000,586boxShadow: "2px 2px 2px 2px #bbb",587}}588/>589</div>590);591}592593594