Path: blob/master/src/packages/frontend/custom-software/init.ts
1496 views
/*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 { Map as iMap } from "immutable";910import { redux, Store, Actions, Table } from "@cocalc/frontend/app-framework";11import { NAME } from "./util";12import { capitalize } from "@cocalc/util/misc";1314// this must match db-schema.compute_images → field type → allowed values15// "standard" image names are "default", "exp", "ubuntu2020", or a timestamp-string16// custom images are "custom/<image-id>/<tag, usually latest>"17// the "custom/" string is supposed to be CUSTOM_IMG_PREFIX, only for cocalc.com.18export type ComputeImageTypes = "standard" | "custom";1920// this must be compatible with db-schema.compute_images → field keys21export type ComputeImageKeys =22| "id"23| "src"24| "type"25| "display"26| "url"27| "desc"28| "path"29| "search_str"30| "display_tag"31| "disabled";3233export type ComputeImage = iMap<ComputeImageKeys, string>;34export type ComputeImages = iMap<string, ComputeImage>;3536export interface ComputeImagesState {37images?: ComputeImages;38}3940export class ComputeImagesStore extends Store<ComputeImagesState> {}4142export function launchcode2display(43images: ComputeImages,44launch: string,45): string | undefined {46// launch expected to be "csi/some-id/..."47const id = launch.split("/")[1];48if (!id) return undefined;49const img = images.get(id);50if (img == null) return undefined;51return img.get("display") || id2name(id);52}5354export class ComputeImagesActions<55ComputeImagesState,56> extends Actions<ComputeImagesState> {}5758function id2name(id: string): string {59return id.split("-").map(capitalize).join(" ");60}6162function fallback(63img: ComputeImage,64key: ComputeImageKeys,65replace: (img: ComputeImage) => string | undefined,66): string {67const ret = img.get(key);68if (ret == null || ret.length == 0) {69return replace(img) || "";70}71return ret;72}7374function display_fallback(img: ComputeImage, id: string) {75return fallback(img, "display", (_) => id2name(id));76}7778function desc_fallback(img: ComputeImage) {79return fallback(img, "desc", (_) => "*No description available.*");80}8182/* if there is no URL set, derive it from the git source URL83* this supports github, gitlab and bitbucket. https URLs look like this:84* https://github.com/sagemathinc/cocalc.git85* https://gitlab.com/orgname/projectname.git86* https://[email protected]/orgname/projectname.git87*/88function url_fallback(img: ComputeImage) {89const cloudgit = ["github.com", "gitlab.com", "bitbucket.org"];90const derive_url = (img: ComputeImage) => {91const src = img.get("src", undefined);92if (src == null || src.length == 0) return;93if (!src.startsWith("http")) return;94for (const srv of cloudgit) {95if (src.indexOf(srv) < 0) continue;96if (src.endsWith(".git")) {97return src.slice(0, -".git".length);98} else {99return src;100}101}102};103return fallback(img, "url", derive_url);104}105106class ComputeImagesTable extends Table {107constructor(NAME, redux) {108super(NAME, redux);109this._change = this._change.bind(this);110}111112query() {113return NAME;114}115116options(): any[] {117return [];118}119120prepare(data: ComputeImages): ComputeImages {121// console.log("ComputeImagesTable data:", data);122// deriving disp, desc, etc. must be robust against null and empty strings123return (124data125// filter disabled ones. we still want to have the data available, though.126.filter((img) => !img.get("disabled", false))127.map((img, id) => {128const display = display_fallback(img, id);129const desc = desc_fallback(img);130const url = url_fallback(img);131const search_str = `${id} ${display} ${desc} ${url}`132.split(" ")133.filter((x) => x.length > 0)134.join(" ")135.toLowerCase();136// derive the displayed tag, docker-like137const tag = id.indexOf(":") >= 0 ? "" : ":latest";138const disp_tag = `${id}${tag}`;139140return img.withMutations((img) =>141img142.set("display", display)143.set("desc", desc)144.set("search_str", search_str)145.set("url", url)146.set("display_tag", disp_tag),147);148})149);150}151152_change(table, _keys): void {153const store: ComputeImagesStore | undefined = this.redux.getStore(NAME);154if (store == null) throw Error("store must be defined");155const actions = this.redux.getActions(NAME);156if (actions == null) throw Error("actions must be defined");157const data = table.get();158actions.setState({ images: this.prepare(data) });159}160}161162export function init() {163if (!redux.hasStore(NAME)) {164redux.createStore(NAME, ComputeImagesStore, {});165redux.createActions(NAME, ComputeImagesActions);166redux.createTable(NAME, ComputeImagesTable);167}168}169170171