import * as React from "react"; import { JobProgress, ConfigurationName } from "farmbot/dist"; import { SemverResult, semverCompare } from "../../../util"; import { OsUpdateButtonProps } from "./interfaces"; import { checkControllerUpdates } from "../../actions"; import { isString } from "lodash"; import { BotState } from "../../interfaces"; import { Content } from "../../../constants"; import { t } from "../../../i18next_wrapper"; /** FBOS update button states. */ enum UpdateButton { upToDate, needsUpdate, unknown, none } interface ButtonProps { color: "green" | "gray" | "yellow"; text: string; hoverText: string | undefined; } /** FBOS update button state => props. */ const buttonProps = (status: UpdateButton, hoverText: string | undefined): ButtonProps => { switch (status) { case UpdateButton.needsUpdate: return { color: "green", text: t("UPDATE"), hoverText }; case UpdateButton.upToDate: return { color: "gray", text: t("UP TO DATE"), hoverText }; case UpdateButton.unknown: const text = t("Can't connect to release server"); return { color: "yellow", text, hoverText }; default: return { color: "yellow", text: t("Can't connect to bot"), hoverText }; } }; /** FBOS update download in progress. */ const isWorking = (job: JobProgress | undefined) => job && (job.status == "working"); /** FBOS update download progress. */ export function downloadProgress(job: JobProgress | undefined) { if (job && isWorking(job)) { switch (job.unit) { case "bytes": const kiloBytes = Math.round(job.bytes / 1024); const megaBytes = Math.round(job.bytes / 1048576); if (kiloBytes < 1) { return job.bytes + "B"; } else if (megaBytes < 1) { return kiloBytes + "kB"; } else { return megaBytes + "MB"; } case "percent": return job.percent + "%"; } } } /** Determine the latest available version. */ const getLatestVersion = ( currentOSVersion: string | undefined, currentBetaOSVersion: string | undefined, betaOptIn: boolean, ): string | undefined => { if (!betaOptIn) { return currentOSVersion; } switch (semverCompare(currentOSVersion || "", currentBetaOSVersion || "")) { case SemverResult.RIGHT_IS_GREATER: return currentBetaOSVersion; default: return currentOSVersion; } }; /** Determine the installed version. */ const getInstalledVersion = ( controllerVersion: string | undefined, currentlyOnBeta: boolean, ): string | undefined => { if (!isString(controllerVersion)) { return undefined; } if (controllerVersion.includes("beta")) { return controllerVersion; } return currentlyOnBeta ? controllerVersion + "-beta" : controllerVersion; }; /** Unequal beta commits => needs update. */ const betaCommitsAreEqual = ( fbosCommit: string | undefined, currentBetaOSCommit: string | undefined): boolean => !(isString(fbosCommit) && isString(currentBetaOSCommit) && fbosCommit !== currentBetaOSCommit); /** Determine the FBOS update button state. */ const compareWithBotVersion = ( candidate: string | undefined, installedVersion: string | undefined, ): UpdateButton => { if (!isString(installedVersion)) { return UpdateButton.none; } if (!isString(candidate)) { return UpdateButton.unknown; } // If all values are known, match comparison result with button state. switch (semverCompare(candidate, installedVersion)) { case SemverResult.RIGHT_IS_GREATER: case SemverResult.EQUAL: return UpdateButton.upToDate; default: return UpdateButton.needsUpdate; } }; /** Installed version equal to latest. */ const equalToLatest = ( latest: string | undefined, installedVersion: string | undefined, ): boolean => isString(installedVersion) && isString(latest) && semverCompare(installedVersion, latest) === SemverResult.EQUAL; /** Color, text, and hover text for update button: release version status. */ const buttonVersionStatus = ({ bot, betaOptIn }: { bot: BotState, betaOptIn: boolean }): ButtonProps => { // Information about available releases. const { currentOSVersion, currentBetaOSVersion, currentBetaOSCommit } = bot; // Currently installed FBOS version data. const botInfo = bot.hardware.informational_settings; const { controller_version, commit, currently_on_beta, update_available } = botInfo; /** Newest release version, given settings and data available. */ const latestReleaseV = getLatestVersion(currentOSVersion, currentBetaOSVersion, betaOptIn); /** Installed version. */ const installedVersion = getInstalledVersion(controller_version, !!currently_on_beta); /** FBOS update button status. */ const btnStatus = compareWithBotVersion(latestReleaseV, installedVersion); /** Beta update special cases. */ const uncertainty = (btnStatus === UpdateButton.upToDate) && equalToLatest(latestReleaseV, installedVersion) && betaOptIn; /** `1.0.0-beta vs 1.0.0-beta`: installed beta is older. */ const oldBetaCommit = (latestReleaseV === currentBetaOSVersion) && !betaCommitsAreEqual(commit, currentBetaOSCommit); /** Button status modification required for release edge cases. */ const updateStatusOverride = update_available || (uncertainty && oldBetaCommit); return buttonProps( updateStatusOverride ? UpdateButton.needsUpdate : btnStatus, latestReleaseV); }; /** Shows update availability or download progress. Updates FBOS on click. */ export const OsUpdateButton = (props: OsUpdateButtonProps) => { const { bot, sourceFbosConfig, botOnline } = props; const { controller_version } = bot.hardware.informational_settings; /** FBOS beta release opt-in setting. */ const betaOptIn = sourceFbosConfig("update_channel" as ConfigurationName).value !== "stable"; /** FBOS update availability. */ const buttonStatusProps = buttonVersionStatus({ bot, betaOptIn }); /** FBOS update download progress data. */ const osUpdateJob = (bot.hardware.jobs || {})["FBOS_OTA"]; const tooOld = controller_version && (semverCompare("6.0.0", controller_version) === SemverResult.LEFT_IS_GREATER ? Content.TOO_OLD_TO_UPDATE : undefined); return ; };