diff --git a/web/pages/components/main-layout.tsx b/web/pages/components/main-layout.tsx index b8bf3e31d..a100bc0e7 100644 --- a/web/pages/components/main-layout.tsx +++ b/web/pages/components/main-layout.tsx @@ -1,9 +1,10 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import Link from 'next/link'; import { differenceInSeconds } from "date-fns"; import { useRouter } from 'next/router'; import { Layout, Menu } from 'antd'; + import { SettingOutlined, HomeOutlined, @@ -13,6 +14,7 @@ import { MinusSquareFilled, } from '@ant-design/icons'; import classNames from 'classnames'; +import { upgradeVersionAvailable } from "../../utils/apis"; import { parseSecondsToDurationString } from '../../utils/format' import OwncastLogo from './logo'; @@ -41,10 +43,28 @@ export default function MainLayout(props) { ? `Online ${ streamDurationString}` : "Offline"; + const [upgradeVersion, setUpgradeVersion] = useState(null); + const checkForUpgrade = async () => { + try { + const result = await upgradeVersionAvailable(); + setUpgradeVersion(result); + } catch (error) { + console.log("==== error", error); + } + }; + + useEffect(() => { + checkForUpgrade(); + }, []); + const appClass = classNames({ 'owncast-layout': true, [adminStyles.online]: broadcastActive, }) + + const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none'; + const upgradeVersionString = upgradeVersion || ''; + return ( Logs - - Upgrade + + + Upgrade to v{upgradeVersionString} + diff --git a/web/pages/upgrade.tsx b/web/pages/upgrade.tsx index e746792c4..35a40dfbb 100644 --- a/web/pages/upgrade.tsx +++ b/web/pages/upgrade.tsx @@ -1,8 +1,7 @@ import React, { useState, useEffect } from "react"; import ReactMarkdown from "react-markdown"; import { Table, Tag } from "antd"; - -const API_URL = "https://api.github.com/repos/owncast/owncast/releases/latest"; +import { getGithubRelease } from "../utils/apis"; export default function Logs() { const [release, setRelease] = useState({ @@ -16,7 +15,7 @@ export default function Logs() { const getRelease = async () => { try { - const result = await fetchData(API_URL); + const result = await getGithubRelease(); setRelease(result); } catch (error) { console.log("==== error", error); @@ -72,17 +71,3 @@ function AssetTable(assets) { return ; } -async function fetchData(url) { - try { - const response = await fetch(url); - if (!response.ok) { - const message = `An error has occured: ${response.status}`; - throw new Error(message); - } - const json = await response.json(); - return json; - } catch (error) { - console.log(error); - } - return {}; -} diff --git a/web/utils/apis.ts b/web/utils/apis.ts index b0db43f85..fc581818f 100644 --- a/web/utils/apis.ts +++ b/web/utils/apis.ts @@ -34,6 +34,8 @@ export const LOGS_ALL = `${API_LOCATION}logs`; // Get warnings + errors export const LOGS_WARN = `${API_LOCATION}logs/warnings`; +const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest"; + // Current Stream status. // This is literally the same as /api/status except it supports // auth. @@ -61,3 +63,65 @@ export async function fetchData(url) { } return {}; } + +export async function getGithubRelease() { + try { + const response = await fetch(GITHUB_RELEASE_URL); + if (!response.ok) { + const message = `An error has occured: ${response.status}`; + throw new Error(message); + } + const json = await response.json(); + return json; + } catch (error) { + console.log(error); + } + return {}; +} + +// Make a request to the server status API and the Github releases API +// and return a release if it's newer than the server version. +export async function upgradeVersionAvailable() { + const serverVersion = await fetchData(STREAM_STATUS) + const recentRelease = await getGithubRelease(); + let recentReleaseVersion = recentRelease.tag_name; + + if (recentReleaseVersion.substr(0, 1) === 'v') { + recentReleaseVersion = recentReleaseVersion.substr(1) + } + + if (!upToDate(serverVersion.versionNumber, recentReleaseVersion)) { + return recentReleaseVersion + } + + return null; +} + +// Stolen from https://gist.github.com/prenagha/98bbb03e27163bc2f5e4 +const VPAT = /^\d+(\.\d+){0,2}$/; +function upToDate(local, remote) { + if (!local || !remote || local.length === 0 || remote.length === 0) + return false; + if (local === remote) + return true; + if (VPAT.test(local) && VPAT.test(remote)) { + const lparts = local.split('.'); + while(lparts.length < 3) + lparts.push("0"); + const rparts = remote.split('.'); + while (rparts.length < 3) + rparts.push("0"); + // eslint-disable-next-line no-plusplus + for (let i=0; i<3; i++) { + const l = parseInt(lparts[i], 10); + const r = parseInt(rparts[i], 10); + if (l === r) + // eslint-disable-next-line no-continue + continue; + return l > r; + } + return true; + } + return local >= remote; + +} \ No newline at end of file