Path: blob/master/src/packages/frontend/compute/menu.tsx
1503 views
/*1Compute server hamburger menu.2*/34import type { MenuProps } from "antd";5import { Button, Dropdown, Spin } from "antd";6import { useMemo, useState } from "react";7import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";8import { A, Icon } from "@cocalc/frontend/components";9import ShowError from "@cocalc/frontend/components/error";10import {11setServerConfiguration,12setTemplate,13} from "@cocalc/frontend/compute/api";14import openSupportTab from "@cocalc/frontend/support/open";15import CloneModal from "./clone";16import { EditModal } from "./compute-server";17import { LogModal } from "./compute-server-log";18import getTitle from "./get-title";19import { AppLauncherModal } from "./launcher";20import { SerialLogModal } from "./serial-port-output";21import { TitleColorModal } from "./title-color";22import { AutomaticShutdownModal } from "./automatic-shutdown";2324function getServer({ id, project_id }) {25return redux26.getProjectStore(project_id)27.getIn(["compute_servers", `${id}`])28?.toJS();29}3031export function getApps(image) {32const IMAGES = redux.getStore("customize").get("compute_servers_images");33if (IMAGES == null || typeof IMAGES == "string") {34// string when error35return {};36}37let apps =38IMAGES.getIn([image, "apps"])?.toJS() ??39IMAGES.getIn(["defaults", "apps"])?.toJS() ??40{};41if (IMAGES.getIn([image, "jupyterKernels"]) === false) {42apps = { ...apps, jupyterlab: undefined };43}44if (apps["xpra"]) {45if (!apps["xpra"].tip) {46apps["xpra"].tip =47"Launch an X11 Linux Graphical Desktop environment running directly on the compute server.";48}49}50return apps;51}5253function getItems({54id,55project_id,56account_id,57isAdmin,58}: {59id: number;60project_id: string;61account_id: string;62title?: string;63color?: string;64isAdmin?: boolean;65}): MenuProps["items"] {66if (!id) {67return [];68}69const server = getServer({ id, project_id });70if (server == null) {71return [72{73key: "loading",74label: (75<>76Loading... <Spin />77</>78),79disabled: true,80},81];82}83const apps = getApps(server.configuration?.image ?? "defaults");84const is_owner = account_id == server.account_id;8586// will be used for start/stop/etc.87// const is_collab = is_owner || server.configuration?.allowCollaboratorControl;8889const titleAndColor = {90key: "title-color",91icon: <Icon name="colors" />,92disabled: !is_owner,93label: "Edit Title and Color",94};95const automaticShutdown = {96key: "automatic-shutdown",97icon: <Icon name="stopwatch" />,98disabled: server.cloud == "onprem",99label: "Automatic Shutdown & Health Check",100};101const jupyterlab = {102key: "top-jupyterlab",103label: "JupyterLab",104icon: <Icon name="jupyter" />,105disabled:106apps["jupyterlab"] == null ||107server.state != "running" ||108!server.data?.externalIp,109};110const vscode = {111key: "top-vscode",112label: "VS Code",113icon: <Icon name="vscode" />,114disabled:115apps["vscode"] == null ||116server.state != "running" ||117!server.data?.externalIp,118};119const xpra = {120key: "xpra",121label: "X11 Desktop",122icon: <Icon name="desktop" />,123disabled:124apps["xpra"] == null ||125server.state != "running" ||126!server.data?.externalIp,127};128129const optionItems: (130| { key: string; label; icon; disabled?: boolean }131| { type: "divider" }132)[] = [133// {134// key: "dns",135// label: "DNS...",136// icon: <Icon name="network" />,137// },138{139key: "allowCollaboratorControl",140label: "Collaborator Control",141icon: (142<Icon143style={{ fontSize: "12pt" }}144name={145server.configuration?.allowCollaboratorControl146? "check-square"147: "square"148}149/>150),151},152{153key: "ephemeral",154label: "Ephemeral",155icon: (156<Icon157style={{ fontSize: "12pt" }}158name={server.configuration?.ephemeral ? "check-square" : "square"}159/>160),161},162{163type: "divider",164},165];166if (server.cloud == "google-cloud") {167optionItems.push({168key: "autoRestart",169label: "Automatically Restart",170disabled: server.cloud != "google-cloud",171icon: (172<Icon173style={{ fontSize: "12pt" }}174name={server.configuration?.autoRestart ? "check-square" : "square"}175/>176),177});178optionItems.push({179key: "enableNestedVirtualization",180label: "Nested Virtualization",181disabled:182server.cloud != "google-cloud" || server.state != "deprovisioned",183icon: (184<Icon185style={{ fontSize: "12pt" }}186name={187server.configuration?.enableNestedVirtualization188? "check-square"189: "square"190}191/>192),193});194}195if (isAdmin) {196if (optionItems[optionItems.length - 1]?.["type"] != "divider") {197optionItems.push({198type: "divider",199});200}201optionItems.push({202key: "template",203label: "Use as Template",204icon: (205<Icon206style={{ fontSize: "12pt" }}207name={server.template?.enabled ? "check-square" : "square"}208/>209),210});211}212213const options = {214key: "options",215label: "Options",216disabled: !is_owner,217icon: <Icon name="gears" />,218children: [219{220key: "run-app-on",221type: "group",222label: "Configure Server",223children: optionItems,224},225],226};227228const help = {229key: "help",230icon: <Icon name="question-circle" />,231label: "Help",232children: [233{234key: "documentation",235icon: <Icon name="question-circle" />,236label: (237<A href="https://doc.cocalc.com/compute_server.html">Documentation</A>238),239},240{241key: "support",242icon: <Icon name="medkit" />,243label: "Support",244},245{246key: "videos",247icon: <Icon name="youtube" style={{ color: "red" }} />,248label: (249<A href="https://www.youtube.com/playlist?list=PLOEk1mo1p5tJmEuAlou4JIWZFH7IVE2PZ">250Videos251</A>252),253},254{255type: "divider",256},257{258key: "dedicated",259icon: <Icon name="bank" />,260label: "Dedicated Always On Server for 6+ Months...",261},262],263};264265const settings = {266key: "settings",267icon: <Icon name="settings" />,268label: is_owner ? "Settings" : "Details...",269};270271const clone = {272key: "clone",273icon: <Icon name="copy" />,274label: "Clone Server Configuration",275};276277return [278titleAndColor,279// {280// type: "divider",281// },282// {283// key: "new-jupyter",284// label: "New Jupyter Notebook",285// icon: <Icon name="jupyter" />,286// disabled: server.state != "running",287// },288// {289// key: "new-terminal",290// label: "New Linux Terminal",291// icon: <Icon name="terminal" />,292// disabled: server.state != "running",293// },294{295type: "divider",296},297jupyterlab,298vscode,299xpra,300{301type: "divider",302},303settings,304automaticShutdown,305//spendLimit,306options,307{308type: "divider",309},310{311key: "control-log",312icon: <Icon name="history" />,313label: "Compute Server Log",314},315{316key: "serial-console-log",317disabled:318server.cloud != "google-cloud" ||319server.state == "off" ||320server.state == "deprovisioned",321icon: <Icon name="laptop" />,322label: "Serial Console",323},324{325type: "divider",326},327clone,328{329type: "divider",330},331help,332// {333// key: "control",334// icon: <Icon name="wrench" />,335// label: "Control",336// children: [337// {338// key: "start",339// icon: <Icon name="play" />,340// label: "Start",341// },342// {343// key: "suspend",344// icon: <Icon name="pause" />,345// label: "Suspend",346// },347// {348// key: "stop",349// icon: <Icon name="stop" />,350// label: "Stop",351// },352// {353// key: "reboot",354// icon: <Icon name="redo" />,355// label: "Hard Reboot",356// danger: true,357// },358// {359// key: "deprovision",360// icon: <Icon name="trash" />,361// label: "Deprovision",362// danger: true,363// },364// {365// key: "delete",366// icon: <Icon name="trash" />,367// label: "Delete",368// danger: true,369// },370// ],371// },372// {373// key: "files",374// label: "Files",375// icon: <Icon name="files" />,376// children: [377// {378// key: "explorer",379// label: "Explorer",380// icon: <Icon name="folder-open" />,381// },382// {383// type: "divider",384// },385// {386// key: "sync",387// icon: <Icon name="sync" />,388// label: "Sync Files",389// },390// {391// key: "disk",392// icon: <Icon name="disk-drive" />,393// label: "Disk Space",394// },395// {396// type: "divider",397// },398// { key: "file1", label: "foo.ipynb", icon: <Icon name="jupyter" /> },399// { key: "file2", label: "tmp/a.term", icon: <Icon name="terminal" /> },400// {401// key: "file3",402// label: "compoute-server-38/foo-bar.ipynb",403// icon: <Icon name="jupyter" />,404// },405// {406// key: "file4",407// label: "compoute-server-38/example.ipynb",408// icon: <Icon name="jupyter" />,409// },410// ],411// },412];413}414415export default function Menu({416id,417project_id,418style,419fontSize,420size,421}: {422id: number;423project_id: string;424style?;425fontSize?;426size?;427}) {428const [error, setError] = useState<string>("");429const [open, setOpen] = useState<boolean>(false);430const account_id = useTypedRedux("account", "account_id");431const [modal, setModal] = useState<any>(null);432const close = () => setModal(null);433const [title, setTitle] = useState<{434title: string;435color: string;436project_specific_id: number;437} | null>(null);438const isAdmin = useTypedRedux("account", "is_admin");439const { items, onClick } = useMemo(() => {440if (!open) {441return { onClick: () => {}, items: [] };442}443444(async () => {445setTitle(await getTitle(id));446})();447return {448items: getItems({ id, project_id, account_id, isAdmin }),449onClick: async (obj) => {450setOpen(false);451let cmd = obj.key.startsWith("top-") ? obj.key.slice(4) : obj.key;452switch (cmd) {453case "control-log":454setModal(<LogModal id={id} close={close} />);455break;456457case "settings":458setModal(459<EditModal id={id} project_id={project_id} close={close} />,460);461break;462463case "clone":464setModal(<CloneModal id={id} close={close} />);465break;466467case "serial-console-log":468setModal(469<SerialLogModal470id={id}471title={title?.title ?? ""}472close={close}473/>,474);475break;476477case "vscode":478case "jupyterlab":479case "xpra":480setModal(481<AppLauncherModal482name={cmd}483id={id}484project_id={project_id}485close={close}486/>,487);488break;489490case "title-color":491setModal(492<TitleColorModal id={id} project_id={project_id} close={close} />,493);494break;495496case "automatic-shutdown":497setModal(498<AutomaticShutdownModal499id={id}500project_id={project_id}501close={close}502/>,503);504break;505506case "ephemeral":507case "allowCollaboratorControl":508case "autoRestart":509case "enableNestedVirtualization":510case "template":511const server = getServer({ id, project_id });512if (server != null) {513try {514if (obj.key == "template") {515await setTemplate({516id,517template: { enabled: !server.template?.enabled },518});519} else {520await setServerConfiguration({521id,522configuration: {523[cmd]: !server.configuration?.[cmd],524},525});526}527} catch (err) {528setError(`${err}`);529}530}531break;532533case "documentation":534case "videos":535// click opens new tab anyways536break;537538case "support":539openSupportTab({540type: "question",541subject: `Compute Server (Global Id: ${id}; Project Specific Id: ${title?.project_specific_id})`,542body: `I am using a compute server, and have a question...`,543});544break;545546case "dedicated":547openSupportTab({548type: "question",549subject: `Compute Server (Global Id: ${id}; Project Specific Id: ${title?.project_specific_id})`,550body: `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!`,551});552break;553554default:555setError(`not implemented -- '${cmd}'`);556}557},558};559}, [id, project_id, open, title]);560561return (562<div style={style}>563<Dropdown564menu={{ items, onClick }}565trigger={["click"]}566onOpenChange={setOpen}567>568<Button type="text" size={size}>569<Icon570name="ellipsis"571style={{ fontSize: fontSize ?? "15pt", color: "#000" }}572rotate="90"573/>574</Button>575</Dropdown>576{modal}577<ShowError578error={error}579setError={setError}580style={{581fontWeight: "normal",582whiteSpace: "normal",583position: "absolute",584right: 0,585maxWidth: "500px",586zIndex: 1000,587boxShadow: "2px 2px 2px 2px #bbb",588}}589/>590</div>591);592}593594595