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/select-image.tsx
Views: 687
import type {1Architecture,2State,3Configuration,4Images,5GoogleCloudImages,6} from "@cocalc/util/db-schema/compute-servers";7import { makeValidGoogleName } from "@cocalc/util/db-schema/compute-servers";8import { Alert, Checkbox, Select, Spin } from "antd";9import { CSSProperties, useEffect, useMemo, useState } from "react";10import { Icon, Markdown } from "@cocalc/frontend/components";11import { A } from "@cocalc/frontend/components/A";12import { field_cmp, trunc } from "@cocalc/util/misc";13import { useImages } from "./images-hook";14import SelectVersion from "./select-version";15import Advanced from "./advanced";16import { RenderImage } from "./images";1718interface Props {19setConfig;20configuration: Configuration;21disabled?: boolean;22state?: State;23style?: CSSProperties;24// if explicitly set, only gpu images shown when25// gpu true, and only non-gpu when false.26gpu: boolean;27// if googleImages is set, use this to restrict list of images to only28// what is actually available in non-advanced view, and to enhance the29// view otherwise (explicitly saying images aren't actually available)30googleImages?: GoogleCloudImages;31arch: Architecture;32// if specified, only show images with dockerSizeGb set and <= maxDockerSizeGb33// Ignored if advanced is selected34maxDockerSizeGb?: number;35// show a warning if dockerSizeGb is bigger than this:36warnBigGb?: number;37}3839export default function SelectImage({40setConfig,41configuration,42disabled,43state = "deprovisioned",44style,45gpu,46googleImages,47arch,48maxDockerSizeGb,49warnBigGb,50}: Props) {51const [advanced, setAdvanced] = useState<boolean>(false);52const [IMAGES, ImagesError] = useImages();53const [dockerSizeGb, setDockerSizeGb] = useState<number | undefined>(54undefined,55);56const [value, setValue] = useState<string | undefined>(configuration.image);57useEffect(() => {58setValue(configuration.image);59}, [configuration.image]);6061const options = useMemo(() => {62if (IMAGES == null || typeof IMAGES == "string") {63return [];64}65return getOptions({66IMAGES,67googleImages,68gpu,69advanced,70value,71selectedTag: configuration.tag,72arch,73maxDockerSizeGb,74});75}, [IMAGES, gpu, advanced, value, configuration.tag]);7677if (IMAGES == null) {78return <Spin />;79}80if (ImagesError != null) {81return ImagesError;82}83const filterOption = (input: string, option?: { search: string }) =>84(option?.search ?? "").includes(input.toLowerCase());8586return (87<div>88<Advanced89advanced={advanced}90setAdvanced={setAdvanced}91style={{ float: "right", marginTop: "10px" }}92title={93"Show possibly untested, old, missing, or broken images and versions."94}95/>96<Select97size="large"98disabled={disabled || state != "deprovisioned"}99placeholder="Select compute server image..."100defaultOpen={!value && state == "deprovisioned"}101value={value}102style={{ width: "500px", ...style }}103options={options}104onChange={(val) => {105setValue(val);106const x = {107image: val,108tag: null,109};110for (const option of options) {111if (option.value == val) {112setDockerSizeGb(option.dockerSizeGb);113break;114}115}116setConfig(x);117}}118showSearch119filterOption={filterOption}120/>121{advanced && IMAGES != null && typeof IMAGES != "string" && value && (122<SelectVersion123style={{ margin: "10px 0" }}124disabled={disabled || state != "deprovisioned"}125image={value}126IMAGES={IMAGES}127setConfig={setConfig}128configuration={configuration}129/>130)}131{warnBigGb && (dockerSizeGb ?? 1) > warnBigGb && (132<Alert133style={{ margin: "15px 0" }}134type="warning"135message={<h4>Large Image Warning</h4>}136description={137<>138The compute server will take{" "}139<b>up to {Math.ceil((dockerSizeGb ?? 1) / 3)} extra minutes</b> to140start the first time, because a {dockerSizeGb} GB Docker image141must be pulled and decompressed. Please be patient!142<br />143<br />144<Checkbox>145I understand that initial startup will take at least{" "}146{Math.ceil((dockerSizeGb ?? 1) / 3)} extra minutes147</Checkbox>148</>149}150/>151)}152</div>153);154}155156function getOptions({157IMAGES,158advanced,159googleImages,160gpu,161value,162selectedTag,163arch,164maxDockerSizeGb,165}: {166IMAGES: Images;167advanced?: boolean;168gpu?: boolean;169value?: string;170selectedTag?: string;171googleImages?: GoogleCloudImages;172arch: Architecture;173maxDockerSizeGb?: number;174}) {175const options: {176key: string;177tag: string;178priority: number;179value: string;180search: string;181label: JSX.Element;182dockerSizeGb?: number;183}[] = [];184for (const name in IMAGES) {185const image = IMAGES[name];186let { label, icon, versions, priority = 0, dockerSizeGb } = image;187if (image.system) {188continue;189}190if (image.disabled && !advanced) {191continue;192}193if (gpu != null && gpu != image.gpu) {194continue;195}196if (!advanced && maxDockerSizeGb != null) {197if (dockerSizeGb == null || dockerSizeGb > maxDockerSizeGb) {198continue;199}200}201if (!advanced) {202// restrict to only tested versions.203versions = versions.filter((x) => x.tested);204205if (googleImages != null) {206const x = googleImages[name];207// on google cloud, so make sure image is built and tested208versions = versions.filter(209(y) =>210x?.[`${makeValidGoogleName(y.tag)}-${makeValidGoogleName(arch)}`]211?.tested,212);213}214}215if (versions.length == 0) {216// no available versions, so no point in showing this option217continue;218}219let tag;220let versionLabel: string | undefined = undefined;221if (selectedTag && name == value) {222tag = selectedTag;223for (const x of versions) {224if (x.tag == tag) {225versionLabel = x.label ?? tag;226break;227}228}229} else {230tag = versions[versions.length - 1]?.tag;231versionLabel = versions[versions.length - 1]?.label ?? tag;232}233234let extra = "";235if (advanced && googleImages != null) {236const img =237googleImages[name]?.[238`${makeValidGoogleName(tag)}-${makeValidGoogleName(arch)}`239];240if (!img) {241extra = " (no image)";242} else {243const tested = img?.tested;244if (!tested) {245extra = " (not tested)";246}247}248}249if (dockerSizeGb) {250extra += ` - ${dockerSizeGb} GB`;251}252253options.push({254key: name,255value: name,256priority,257search: label?.toLowerCase() ?? "",258tag,259dockerSizeGb,260label: (261<div style={{ fontSize: "12pt" }}>262<div style={{ float: "right" }}>{versionLabel}</div>263<Icon name={icon} style={{ marginRight: "5px" }} /> {label}264{image.disabled && <> (disabled)</>}265{extra}266</div>267),268});269}270options.sort(field_cmp("priority")).reverse();271return options;272}273274export function ImageLinks({ image, style }: { image; style? }) {275const [IMAGES, ImagesError] = useImages();276if (IMAGES == null) {277return <Spin />;278}279if (typeof IMAGES == "string") {280return ImagesError;281}282const data = IMAGES[image];283if (data == null) {284return null;285}286return (287<div288style={{289display: "flex",290flexDirection: "column",291marginTop: "10px",292...style,293}}294>295{data.videos != null && data.videos.length > 0 && (296<A style={{ flex: 1 }} href={data.videos[0]}>297<Icon name="youtube" style={{ color: "red" }} /> YouTube298</A>299)}300{data.tutorials != null && data.tutorials.length > 0 && (301<A style={{ flex: 1 }} href={data.tutorials[0]}>302<Icon name="graduation-cap" /> Tutorial303</A>304)}305<A style={{ flex: 1 }} href={data.source}>306<Icon name="github" /> GitHub307</A>308<A style={{ flex: 1 }} href={data.url}>309<Icon name="external-link" /> {trunc(data.label, 10)}310</A>311<A style={{ flex: 1 }} href={packageNameToUrl(data.package)}>312<Icon name="docker" /> DockerHub313</A>314</div>315);316}317318// this is a heuristic but is probably right in many cases, and319// right now the only case is n<=1, where it is right.320function packageNameToUrl(name: string): string {321const n = name.split("/").length - 1;322if (n <= 1) {323return `https://hub.docker.com/r/${name}`;324} else {325// e.g., us-docker.pkg.dev/colab-images/public/runtime326return `https://${name}`;327}328}329330export function DisplayImage({331configuration,332style,333}: {334configuration: { image: string };335style?;336}) {337const [IMAGES, ImagesError] = useImages();338if (ImagesError != null) {339return ImagesError;340}341return (342<RenderImage configuration={configuration} style={style} IMAGES={IMAGES} />343);344}345346export function ImageDescription({347configuration,348}: {349configuration: { image: string };350}) {351const [IMAGES, ImagesError] = useImages();352if (IMAGES == null) {353return <Spin />;354}355if (typeof IMAGES == "string") {356return ImagesError;357}358return (359<Alert360style={{ padding: "7.5px 15px", marginTop: "10px" }}361type="info"362description={363<Markdown364value={IMAGES[configuration?.image ?? ""]?.description ?? ""}365/>366}367/>368);369}370371372