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/next/components/store/quota-query-params.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { testDedicatedDiskNameBasic } from "@cocalc/util/licenses/check-disk-name-basics";6import { BOOST, REGULAR } from "@cocalc/util/upgrades/consts";7import {8DEDICATED_DISK_SIZES,9DEDICATED_DISK_SPEEDS,10DEFAULT_DEDICATED_DISK_SIZE,11DEFAULT_DEDICATED_DISK_SPEED,12DEFAULT_DEDICATED_VM_MACHINE,13PRICES,14} from "@cocalc/util/upgrades/dedicated";15import type { DateRange } from "@cocalc/util/upgrades/shopping";16import { clamp, isDate } from "lodash";17import dayjs from "dayjs";18import { NextRouter } from "next/router";19import { MAX_ALLOWED_RUN_LIMIT } from "./run-limit";2021// Various support functions for storing quota parameters as a query parameter in the browser URL2223export function encodeRange(24vals: [Date | string | undefined, Date | string | undefined]25): string {26const [start, end] = vals;27if (start == null || end == null) {28return "";29}30return `${new Date(start).toISOString()}_${new Date(end).toISOString()}`;31}3233// the inverse of encodeRange34function decodeRange(val: string): DateRange {35if (!val) return [undefined, undefined];36const vals = val.split("_");37if (vals.length != 2) return [undefined, undefined];38const w: Date[] = [];39for (const x of vals) {40const d = dayjs(x);41if (d.isValid()) {42w.push(d.toDate());43} else {44return [undefined, undefined];45}46}47return w as DateRange;48}4950const COMMON_FIELDS = [51"user",52"period",53"range",54"title",55"description",56] as const;5758const REGULAR_FIELDS = [59...COMMON_FIELDS,60"run_limit",61"member",62"uptime",63"cpu",64"ram",65"disk",66] as const;6768const DEDICATED_FIELDS = [69...COMMON_FIELDS,70"disk-size_gb",71"disk-name",72"disk-speed",73"vm-machine",74] as const;7576function getFormFields(77type: "regular" | "boost" | "dedicated"78): readonly string[] {79switch (type) {80case "regular":81case "boost":82return REGULAR_FIELDS;83case "dedicated":84return DEDICATED_FIELDS;85}86}8788export const ALL_FIELDS: Set<string> = new Set(89REGULAR_FIELDS.concat(DEDICATED_FIELDS as any).concat(["type" as any])90);9192export function encodeFormValues(93router: NextRouter,94vals: any,95type: "regular" | "boost" | "dedicated"96): void {97const { query } = router;98for (const key in vals) {99if (!getFormFields(type).includes(key)) continue;100const val = vals[key];101if (val == null) {102delete query[key];103} else if (key === "range") {104query[key] = encodeRange(val);105} else {106query[key] = val;107}108}109router.replace({ query }, undefined, { shallow: true, scroll: false });110}111112function decodeValue(val): boolean | number | string | DateRange {113if (val === "true") return true;114if (val === "false") return false;115const num = Number(val);116if (!isNaN(num)) return num;117return val;118}119120function fixNumVal(121val: any,122param: { min: number; max: number; dflt: number }123): number {124if (typeof val !== "number") {125return param.dflt;126} else {127return clamp(val, param.min, param.max);128}129}130131/** a query looks like this:132* user=academic&period=monthly&run_limit=1&member=true&uptime=short&cpu=1&ram=2&disk=3133*134* NOTE: the support for dedicated disk & vm does not work. the form is too complicated, not no need to support this yet.135*/136export function decodeFormValues(137router: NextRouter,138type: "regular" | "boost" | "dedicated"139): {140[key: string]: string | number | boolean;141} {142const P = type === "boost" ? BOOST : REGULAR;143const fields: readonly string[] = getFormFields(type);144145const data = {};146for (const key in router.query) {147const val = router.query[key];148if (!fields.includes(key)) continue;149if (typeof val !== "string") continue;150data[key] = key === "range" ? decodeRange(val) : decodeValue(val);151}152153// we also have to sanitize the values154for (const key in data) {155const val = data[key];156switch (key) {157case "user":158if (!["academic", "business"].includes(val)) {159data[key] = "academic";160}161break;162163case "period":164if (!["monthly", "yearly", "range"].includes(val)) {165data[key] = "monthly";166}167break;168169case "range":170// check that val is an array of length 2 and both entries are Date objects171if (!Array.isArray(val) || val.length !== 2 || !val.every(isDate)) {172data[key] = [undefined, undefined];173}174break;175176case "run_limit":177// check that val is a number and in the range of 1 to 1000178if (typeof val !== "number" || val < 1 || val > MAX_ALLOWED_RUN_LIMIT) {179data[key] = 1;180}181break;182183case "member":184if (typeof val !== "boolean") {185data[key] = true;186}187break;188189case "uptime":190if (!["short", "medium", "day", "always_running"].includes(val)) {191data[key] = "short";192}193break;194195case "cpu":196data[key] = fixNumVal(val, P.cpu);197break;198199case "ram":200data[key] = fixNumVal(val, P.ram);201break;202203case "disk":204data[key] = fixNumVal(val, P.disk);205break;206207case "disk-size_gb":208if (typeof val !== "number" || !DEDICATED_DISK_SIZES.includes(val)) {209data[key] = DEFAULT_DEDICATED_DISK_SIZE;210}211break;212213case "disk-name":214try {215testDedicatedDiskNameBasic(val);216} catch {217data[key] = "";218}219break;220221case "disk-speed":222if (!DEDICATED_DISK_SPEEDS.includes(val)) {223data[key] = DEFAULT_DEDICATED_DISK_SPEED;224}225break;226227case "vm-machine":228if (PRICES.vms[val] == null) {229data[key] = DEFAULT_DEDICATED_VM_MACHINE;230}231break;232233case "title":234case "description":235data[key] = val;236break;237238default:239console.log(`decodingFormValues: unknown key '${key}'`);240delete data[key];241}242}243244// hosting quality vs. uptime restriction:245if (["always_running", "day"].includes(data["uptime"])) {246data["member"] = true;247}248249if (type === "dedicated") {250data["type"] = data["vm-machine"] != null ? "vm" : null;251252// if any key in data starts with "disk-" then set data["type"] to "disk"253if (data["type"] == null) {254for (const key in data) {255if (key.startsWith("disk-")) {256data["type"] = "disk";257break;258}259}260}261262if (data["type"] === "disk") {263data["period"] = "monthly";264}265if (data["type"] === "vm") {266data["period"] = "range";267}268}269270return data;271}272273274