Path: blob/master/src/packages/frontend/compute/doc-status.tsx
1503 views
/*1This is a component that should be placed at the top of a document to help2the user when they have requested their document run on a given compute3server. It does the following:45- If id is as requested and is the project, do nothing.67- If id is as requested and is not the project, draw line in color of that compute server.89- If not where we want to be, defines how close via a percentage1011- If compute server not running:12- if exists and you own it, prompts user to start it and also shows the13compute server's component so they can do so.14- if not exists (or deleted), say so15- if owned by somebody else, say so16*/1718import Inline from "./inline";19import { useTypedRedux } from "@cocalc/frontend/app-framework";20import { webapp_client } from "@cocalc/frontend/webapp-client";21import { Alert, Button, Progress, Space, Spin, Tooltip } from "antd";22import type { ComputeServerUserInfo } from "@cocalc/util/db-schema/compute-servers";23import ComputeServer from "./compute-server";24import { useEffect, useMemo, useState } from "react";25import { Icon } from "@cocalc/frontend/components";26import SyncButton from "./sync-button";27import { avatar_fontcolor } from "@cocalc/frontend/account/avatar/font-color";28import { DisplayImage } from "./select-image";29import Menu from "./menu";30import { SpendLimitStatus } from "./spend-limit";3132interface Props {33project_id: string;34id: number;35requestedId?: number;36noSync?: boolean;37standalone?: boolean;38}3940export function ComputeServerDocStatus({41project_id,42id,43requestedId,44noSync,45standalone,46}: Props) {47if (requestedId == null) {48requestedId = id;49}50const [showDetails, setShowDetails] = useState<boolean | null>(null);51const computeServers = useTypedRedux({ project_id }, "compute_servers");52const account_id = useTypedRedux("account", "account_id");5354useEffect(() => {55// if the id or requestedId changes, need to reset to default behavior56// regarding what is shown.57setShowDetails(null);58}, [id, requestedId]);5960const requestedServer = computeServers?.get(`${requestedId}`);61const server: ComputeServerUserInfo | undefined = useMemo(62() => requestedServer?.toJS(),63[requestedServer],64);65const syncExclude = requestedServer?.getIn([66"configuration",67"excludeFromSync",68]);69const excludeFromSync =70syncExclude?.includes("~") || syncExclude?.includes(".");71const syncState = requestedServer?.getIn([72"detailed_state",73"filesystem-sync",74]);7576// show sync errors77useEffect(() => {78if (syncState?.get("extra")) {79setShowDetails(true);80}81}, [syncState?.get("extra")]);8283if (id == 0 && requestedId == 0) {84return null;85}8687if (computeServers == null) {88return null;89}9091const topBar = (progress) => (92<div93style={{94display: "flex",95borderBottom:96!standalone && requestedServer != null && !showDetails97? "1px solid #ccc"98: undefined,99...(standalone100? { border: "1px solid #ddd", borderRadius: "5px" }101: undefined),102}}103>104{progress == 100 && !noSync && (105<SyncButton106type="text"107disabled={excludeFromSync}108style={{109marginLeft: "-3px",110float: "right",111width: "90px",112}}113size="small"114compute_server_id={id}115project_id={project_id}116time={syncState?.get("time")}117syncing={118requestedServer?.get("state") == "running" &&119!syncState?.get("extra") &&120(syncState?.get("progress") ?? 100) <12180 /* 80 because the last per for read cache is not sync and sometimes gets stuck */122}123>124Sync125</SyncButton>126)}127{progress < 100 && (128<Tooltip title={"Make sure the compute server is running."}>129<div130onClick={() => {131setShowDetails(showDetails === true ? false : true);132}}133style={{134whiteSpace: "nowrap",135padding: "2.5px 5px",136background: "darkred",137color: "white",138height: "24px",139}}140>141NOT CONNECTED142</div>143</Tooltip>144)}145<Tooltip146mouseEnterDelay={0.9}147title={148<>149{progress == 100 ? "Running on " : "Opening on "}{" "}150<Inline id={requestedId} computeServer={requestedServer} />.151</>152}153>154<div155onClick={() => {156setShowDetails(showDetails === true ? false : true);157}}158style={{159height: "24px",160cursor: "pointer",161padding: "2px 5px",162background: requestedServer?.get("color") ?? "#fff",163color: avatar_fontcolor(requestedServer?.get("color") ?? "#fff"),164width: "100%",165overflow: "hidden",166textAlign: "center",167}}168>169{progress < 100 ? `${progress}% - ` : ""}170<div style={{ display: "inline-block" }}>171<div style={{ display: "flex" }}>172<div173style={{174maxWidth: "30ex",175textOverflow: "ellipsis",176overflow: "hidden",177whiteSpace: "nowrap",178marginRight: "5px",179}}180>181{requestedServer?.get("title") ?? "Loading..."}182</div>183(Id: {requestedServer?.get("project_specific_id")})184</div>185</div>186<DisplayImage187style={{188marginLeft: "10px",189borderLeft: "1px solid black",190paddingLeft: "10px",191}}192configuration={requestedServer?.get("configuration")?.toJS()}193/>194</div>195</Tooltip>196{requestedServer != null && (197<SpendLimitStatus server={server} horizontal />198)}199<Menu200fontSize={"13pt"}201size="small"202style={{ marginTop: "1px", height: "10px" }}203id={requestedId}204project_id={project_id}205/>206</div>207);208209const { progress, message, status } = getProgress(210server,211account_id,212id,213requestedId,214);215if (!showDetails) {216if (showDetails == null && progress < 100) {217setShowDetails(true);218}219return topBar(progress);220}221222return (223<div224className="smc-vfill"225style={{ flex: 3, minHeight: "300px", background: "white" }}226>227<div>{topBar(progress)}</div>228<div229className="smc-vfill"230style={{231border: `1px solid #ccc`,232borderRadius: "5px",233margin: "15px",234padding: "5px",235boxShadow: "rgba(33, 33, 33, 0.5) 1px 5px 7px",236marginTop: "0px",237overflow: "auto",238}}239>240<div241style={{242textAlign: "center",243}}244>245<Space style={{ width: "100%", margin: "15px 0" }}>246<Button247size="large"248type="text"249onClick={() => setShowDetails(false)}250>251<Icon name="times" /> Hide252</Button>253<Progress254type="circle"255trailColor="#e6f4ff"256percent={progress}257strokeWidth={14}258size={42}259/>260<Alert261style={{ margin: "0 15px" }}262type="info"263message={264<>265{message}{" "}266{progress < 100 && status != "exception" ? (267<Spin style={{ marginLeft: "15px" }} />268) : undefined}269</>270}271/>272</Space>273</div>274{server != null && (275<ComputeServer276editable={account_id == server.account_id}277server={server}278/>279)}280</div>281</div>282);283}284285// gets progress of starting the compute server with given id and having it actively available to host this file.286function getProgress(287server: ComputeServerUserInfo | undefined,288account_id,289id,290requestedId,291): {292progress: number;293message: string;294status: "exception" | "active" | "normal" | "success";295} {296if (requestedId == 0) {297return {298progress: 50,299message: "Moving back to project...",300status: "active",301};302}303if (server == null) {304return {305progress: 0,306message: "Server does not exist. Please select a different server.",307status: "exception",308};309}310if (server.deleted) {311return {312progress: 0,313message:314"Server was deleted. Please select a different server or undelete it.",315status: "exception",316};317}318319if (320server.account_id != account_id &&321server.state != "running" &&322server.state != "starting"323) {324return {325progress: 0,326message:327"This is not your compute server, and it is not running. Only the owner of a compute server can start it.",328status: "exception",329};330}331332// below here it isn't our server, it is running.333334if (server.state == "deprovisioned") {335return {336progress: 0,337message: "Please start the compute server.",338status: "exception",339};340}341342if (server.state == "off") {343return {344progress: 10,345message: "Please start the compute server.",346status: "exception",347};348}349if (server.state == "suspended") {350return {351progress: 15,352message: "Please resume the compute server.",353status: "exception",354};355}356357if (server.state != "starting" && server.state != "running") {358return {359progress: 25,360message: "Please start the compute server.",361status: "exception",362};363}364365if (server.state == "starting") {366return {367progress: 40,368message: "Compute server is starting.",369status: "active",370};371}372373// below it is running374375const computeIsLive = server.detailed_state?.compute?.state == "ready";376if (computeIsLive) {377if (id == requestedId) {378return {379progress: 100,380message: "Compute server is fully connected!",381status: "success",382};383} else {384return {385progress: 90,386message:387"Compute server is connected and should attach to this file soon...",388status: "success",389};390}391}392const filesystemIsLive =393server.detailed_state?.["filesystem-sync"]?.state == "ready";394const computeIsRecent = isRecent(server.detailed_state?.compute?.time);395const filesystemIsRecent = isRecent(396server.detailed_state?.["filesystem-sync"]?.time,397);398if (filesystemIsRecent) {399return {400progress: 70,401message: "Waiting for filesystem to connect.",402status: "normal",403};404}405if (filesystemIsLive) {406if (computeIsRecent) {407return {408progress: 80,409message: "Waiting for compute to connect.",410status: "normal",411};412}413}414415return {416progress: 50,417message:418"Compute server is running, but filesystem and compute components aren't connected. Waiting...",419status: "active",420};421}422423// This is useful elsewhere to give a sense of how the compute server424// is doing as it progresses from running to really being fully available.425function getRunningStatus(server) {426if (server == null) {427return { progress: 0, message: "Loading...", status: "exception" };428}429return getProgress(server, webapp_client.account_id, server.id, server.id);430}431432export function RunningProgress({433server,434style,435}: {436server: ComputeServerUserInfo | undefined;437style?;438}) {439const { progress, message } = useMemo(() => {440return getRunningStatus(server);441}, [server]);442443return (444<Tooltip title={message}>445<Progress446trailColor="#e6f4ff"447percent={progress}448strokeWidth={14}449style={style}450/>451</Tooltip>452);453}454455function isRecent(expire = 0) {456return Date.now() - expire < 60 * 1000;457}458459460