/// import { Cell, Default, derive, handler, lift, NAME, recipe, UI, } from "commontools"; const DISABLED_VIA_UI = "Disabled via UI"; type BGCharmEntry = { space: string; charmId: string; integration: string; createdAt: number; updatedAt: number; disabledAt: Default; lastRun: Default; status: Default; }; type InputSchema = { charms: Default; }; type ResultSchema = { charms: BGCharmEntry[]; }; const deleteCharm = handler< never, { charms: Cell; charm: Cell } >( (_, { charm, charms }) => { const { space, charmId } = charm.get(); const newList = charms.get().slice(); const index = newList.findIndex((i) => i.space === space && i.charmId === charmId ); if (index >= 0 && index < newList.length) { newList.splice(index, 1); charms.set(newList); } }, ); const toggleCharm = handler }>( (_, { charm }) => { const data = charm.get(); if (data.disabledAt) { charm.set({ ...data, disabledAt: 0, status: "Initializing..." }); } else { charm.set({ ...data, disabledAt: Date.now(), status: DISABLED_VIA_UI }); } }, ); // Minimal "moment" style formatting to get a string // representation of an (older) date relative to now, // e.g. "5 seconds ago". // * Renders "in the future" for all times in the future, // we don't currently need e.g. "5 seconds from now". // * Disregard plural units, "1 minutes ago" is fine. // * Timezones are hard. Could maybe render "0 years ago". function fromNow(then: Date): string { const now = new Date(); const diffSeconds = Math.floor((now.getTime() - then.getTime()) / 1000); if (diffSeconds < 0) return "in the future"; if (diffSeconds === 0) return "now"; if (diffSeconds < 60) return `${diffSeconds} seconds ago`; const diffMinutes = Math.floor(diffSeconds / 60); if (diffMinutes < 60) return `${diffMinutes} minutes ago`; const diffHours = Math.floor(diffMinutes / 60); if (diffHours < 24) return `${diffHours} hours ago`; const diffDays = Math.floor(diffHours / 24); if (diffDays < 365) return `${Math.floor(diffDays)} days ago`; return `${Math.floor(then.getFullYear() - now.getFullYear())} `; } const getRenderData = lift(( charm: BGCharmEntry, ) => { const { integration, space: rawSpace, charmId: rawCharmId, createdAt, updatedAt, disabledAt, lastRun, status, } = charm; const space = rawSpace.slice(-4); const charmId = rawCharmId.slice(-4); const name = `#${space}/#${charmId}`; const createdAtDate = new Date(createdAt); const updatedAtDate = new Date(updatedAt); const lastRunDate = lastRun ? new Date(lastRun) : null; const isSuccessful = status === "Success"; const statusDisplay = isSuccessful ? "" : status; const details = `Created ${ fromNow(createdAtDate) } (${createdAtDate.toLocaleString()}) Updated ${fromNow(updatedAtDate)} (${updatedAtDate.toLocaleString()}) Last run ${lastRunDate ? fromNow(lastRunDate) : "never"} ${ lastRunDate ? `(${lastRunDate.toLocaleString()})` : "" }`; return { statusDisplay, disabledAt, details, integration, name, }; }); const css = ` .bg-charm-container { display: flex; flex-direction: column; } .bg-charm-container .ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .bg-charm-container button { cursor: pointer; } .bg-charm-row { display: flex; flex-direction: row; height: 50px; align-items: center; } .bg-charm-row > * { padding: 10px; } .bg-charm-row .toggle-button, .bg-charm-row .delete { flex: 0; display: flex; } .bg-charm-row .name { width: 250px; cursor: help; } .bg-charm-row .integration { color: #aaa; padding-left: 3px; } .bg-charm-row .status { flex: 1; } .bg-charm-container .delete button { border: 1px solid black; } `; export default recipe( "BG Admin", ({ charms }) => { derive(charms, (charms) => { console.log("bg charm list:", charms); }); return { [NAME]: "BG Updater Management New", [UI]: ( {charms.map((charm) => { const { details, name, integration, statusDisplay, } = getRenderData(charm); return ( {derive(charm, ({ status, disabledAt }) => { const SUCCESS = `#4CAF50`; const UNKNOWN = `#FFC107`; const DISABLED = `#9E9E9E`; const FAILURE = `#F44336`; const statusColor = disabledAt === 0 ? status === "Success" ? SUCCESS : UNKNOWN : status === DISABLED_VIA_UI ? DISABLED : FAILURE; const statusTitle = disabledAt === 0 && status === "Success" ? "Running" : status; return ( ); })} {name} {integration} {statusDisplay} Delete ); })} ), charms, }; }, );