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/custom-software/init.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45// Manage DB <-> UI integration of available *custom* compute images6// TODO: also get rid of hardcoded official software images78import { redux, Store, Actions, Table } from "@cocalc/frontend/app-framework";9import { Map as iMap } from "immutable";10import { NAME } from "./util";11import { capitalize } from "@cocalc/util/misc";1213// this must match db-schema.compute_images → field type → allowed values14// "standard" image names are "default", "exp", "ubuntu2020", or a timestamp-string15// custom iamges are "custom/<image-id>/<tag, usually latest>"16// the "custom/" string is supposed to be CUSTOM_IMG_PREFIX!17export type ComputeImageTypes = "default" | "standard" | "custom";1819// this must be compatible with db-schema.compute_images → field keys20export type ComputeImageKeys =21| "id"22| "src"23| "type"24| "display"25| "url"26| "desc"27| "path"28| "search_str"29| "display_tag"30| "disabled";3132export type ComputeImage = iMap<ComputeImageKeys, string>;33export type ComputeImages = iMap<string, ComputeImage>;3435export interface ComputeImagesState {36images?: ComputeImages;37}3839export class ComputeImagesStore extends Store<ComputeImagesState> {}4041export function launchcode2display(42images: ComputeImages,43launch: string44): string | undefined {45// launch expected to be "csi/some-id/..."46const id = launch.split("/")[1];47if (!id) return undefined;48const img = images.get(id);49if (img == null) return undefined;50return img.get("display") || id2name(id);51}5253export class ComputeImagesActions<54ComputeImagesState55> extends Actions<ComputeImagesState> {}5657function id2name(id: string): string {58return id.split("-").map(capitalize).join(" ");59}6061function fallback(62img: ComputeImage,63key: ComputeImageKeys,64replace: (img: ComputeImage) => string | undefined65): string {66const ret = img.get(key);67if (ret == null || ret.length == 0) {68return replace(img) || "";69}70return ret;71}7273function display_fallback(img: ComputeImage, id: string) {74return fallback(img, "display", (_) => id2name(id));75}7677function desc_fallback(img: ComputeImage) {78return fallback(img, "desc", (_) => "*No description available.*");79}8081/* if there is no URL set, derive it from the git source URL82* this supports github, gitlab and bitbucket. https URLs look like this:83* https://github.com/sagemathinc/cocalc.git84* https://gitlab.com/orgname/projectname.git85* https://[email protected]/orgname/projectname.git86*/87function url_fallback(img: ComputeImage) {88const cloudgit = ["github.com", "gitlab.com", "bitbucket.org"];89const derive_url = (img: ComputeImage) => {90const src = img.get("src", undefined);91if (src == null || src.length == 0) return;92if (!src.startsWith("http")) return;93for (const srv of cloudgit) {94if (src.indexOf(srv) < 0) continue;95if (src.endsWith(".git")) {96return src.slice(0, -".git".length);97} else {98return src;99}100}101};102return fallback(img, "url", derive_url);103}104105class ComputeImagesTable extends Table {106constructor(NAME, redux) {107super(NAME, redux);108this._change = this._change.bind(this);109}110111query() {112return NAME;113}114115options(): any[] {116return [];117}118119prepare(data: ComputeImages): ComputeImages {120// console.log("ComputeImagesTable data:", data);121// deriving disp, desc, etc. must be robust against null and empty strings122return (123data124// filter disabled ones. we still want to have the data available, though.125.filter((img) => !img.get("disabled", false))126.map((img, id) => {127const display = display_fallback(img, id);128const desc = desc_fallback(img);129const url = url_fallback(img);130const search_str = `${id} ${display} ${desc} ${url}`131.split(" ")132.filter((x) => x.length > 0)133.join(" ")134.toLowerCase();135// derive the displayed tag, docker-like136const tag = id.indexOf(":") >= 0 ? "" : ":latest";137const disp_tag = `${id}${tag}`;138139return img.withMutations((img) =>140img141.set("display", display)142.set("desc", desc)143.set("search_str", search_str)144.set("url", url)145.set("display_tag", disp_tag)146);147})148);149}150151_change(table, _keys): void {152const store: ComputeImagesStore | undefined = this.redux.getStore(NAME);153if (store == null) throw Error("store must be defined");154const actions = this.redux.getActions(NAME);155if (actions == null) throw Error("actions must be defined");156const data = table.get();157actions.setState({ images: this.prepare(data) });158}159}160161export function init() {162if (!redux.hasStore(NAME)) {163redux.createStore(NAME, ComputeImagesStore, {});164redux.createActions(NAME, ComputeImagesActions);165redux.createTable(NAME, ComputeImagesTable);166}167}168169170