From f339a2e2cfee4d2faedc5ea2bb93de332dd8047b Mon Sep 17 00:00:00 2001 From: orangecoding Date: Sat, 20 Sep 2025 19:37:27 +0200 Subject: [PATCH] adding version banner to check if a new version of fredy is available --- conf/config.json | 2 +- lib/api/api.js | 3 ++ lib/api/routes/versionRouter.js | 30 ++++++++++++++ lib/services/tracking/Tracker.js | 16 +------- lib/utils.js | 20 ++++++++- ui/src/App.jsx | 6 ++- ui/src/components/version/VersionBanner.jsx | 43 ++++++++++++++++++++ ui/src/components/version/VersionBanner.less | 3 ++ ui/src/services/state/store.js | 14 +++++++ 9 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 lib/api/routes/versionRouter.js create mode 100644 ui/src/components/version/VersionBanner.jsx create mode 100644 ui/src/components/version/VersionBanner.less diff --git a/conf/config.json b/conf/config.json index eb00028..3f8d054 100644 --- a/conf/config.json +++ b/conf/config.json @@ -1 +1 @@ -{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":null,"sqlitepath":"/db"} \ No newline at end of file +{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":true,"sqlitepath":"/db"} \ No newline at end of file diff --git a/lib/api/api.js b/lib/api/api.js index 194855c..14dc00e 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -7,6 +7,7 @@ import { loginRouter } from './routes/loginRoute.js'; import { config } from '../utils.js'; import { userRouter } from './routes/userRoute.js'; import { jobRouter } from './routes/jobRouter.js'; +import { versionRouter } from './routes/versionRouter.js'; import bodyParser from 'body-parser'; import restana from 'restana'; import files from 'serve-static'; @@ -23,6 +24,7 @@ service.use(cookieSession()); service.use(staticService); service.use('/api/admin', authInterceptor()); service.use('/api/jobs', authInterceptor()); +service.use('/api/version', authInterceptor()); // /admin can only be accessed when user is having admin permissions service.use('/api/admin', adminInterceptor()); service.use('/api/jobs/notificationAdapter', notificationAdapterRouter); @@ -30,6 +32,7 @@ service.use('/api/admin/generalSettings', generalSettingsRouter); service.use('/api/jobs/provider', providerRouter); service.use('/api/jobs/insights', analyticsRouter); service.use('/api/admin/users', userRouter); +service.use('/api/version', versionRouter); service.use('/api/jobs', jobRouter); service.use('/api/login', loginRouter); //this route is unsecured intentionally as it is being queried from the login page diff --git a/lib/api/routes/versionRouter.js b/lib/api/routes/versionRouter.js new file mode 100644 index 0000000..1db98a3 --- /dev/null +++ b/lib/api/routes/versionRouter.js @@ -0,0 +1,30 @@ +import restana from 'restana'; +import fetch from 'node-fetch'; +import { getPackageVersion } from '../../utils.js'; + +const service = restana(); +const versionRouter = service.newRouter(); + +versionRouter.get('/', async (req, res) => { + const versionPayload = await getCurrentVersionFromGithub(); + res.body = versionPayload == null ? { newVersion: false } : versionPayload; + res.send(); +}); + +async function getCurrentVersionFromGithub() { + const raw = await fetch('https://api.github.com/repos/orangecoding/fredy/releases/latest'); + const data = await raw.json(); + const localFredyVersion = await getPackageVersion(); + if (localFredyVersion === data.tag_name) { + return null; + } + return { + newVersion: true, + version: data.tag_name, + url: data.html_url, + body: data.body, + localFredyVersion, + }; +} + +export { versionRouter }; diff --git a/lib/services/tracking/Tracker.js b/lib/services/tracking/Tracker.js index 154f0d5..a3e2f29 100644 --- a/lib/services/tracking/Tracker.js +++ b/lib/services/tracking/Tracker.js @@ -1,9 +1,7 @@ import { getJobs } from '../storage/jobStorage.js'; import { getUniqueId } from './uniqueId.js'; -import { config, inDevMode } from '../../utils.js'; +import { config, getPackageVersion, inDevMode } from '../../utils.js'; import os from 'os'; -import { readFileSync } from 'fs'; -import { packageUp } from 'package-up'; import fetch from 'node-fetch'; import logger from '../logger.js'; @@ -77,15 +75,3 @@ function enrichTrackingObject(trackingObject) { version, }; } - -async function getPackageVersion() { - try { - const packagePath = await packageUp(); - const packageJson = readFileSync(packagePath, 'utf8'); - const json = JSON.parse(packageJson); - return json.version; - } catch (error) { - logger.error('Error reading version from package.json', error); - } - return 'N/A'; -} diff --git a/lib/utils.js b/lib/utils.js index 8d2de8d..d752339 100755 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,8 +3,9 @@ import { fileURLToPath } from 'node:url'; import { readFile } from 'fs/promises'; import { createHash } from 'crypto'; import { DEFAULT_CONFIG } from './defaultConfig.js'; -import fs from 'fs'; +import fs, { readFileSync } from 'fs'; import logger from './services/logger.js'; +import { packageUp } from 'package-up'; const RE_GT = />/g; const RE_WEBP = /\/format\/webp/gi; @@ -196,6 +197,22 @@ const normalizeImageUrl = (url) => { return u; }; +/** + * returns Fredy's version + * @returns {Promise<*|string>} + */ +async function getPackageVersion() { + try { + const packagePath = await packageUp(); + const packageJson = readFileSync(packagePath, 'utf8'); + const json = JSON.parse(packageJson); + return json.version; + } catch (error) { + logger.error('Error reading version from package.json', error); + } + return 'N/A'; +} + await refreshConfig(); export { isOneOf }; @@ -206,6 +223,7 @@ export { duringWorkingHoursOrNotSet }; export { getDirName }; export { config }; export { buildHash }; +export { getPackageVersion }; export default { isOneOf, nullOrEmpty, diff --git a/ui/src/App.jsx b/ui/src/App.jsx index 24d0162..5c6aa77 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -18,11 +18,13 @@ import Jobs from './views/jobs/Jobs'; import './App.less'; import TrackingModal from './components/tracking/TrackingModal.jsx'; import { Banner } from '@douyinfe/semi-ui'; +import VersionBanner from './components/version/VersionBanner.jsx'; export default function FredyApp() { const actions = useActions(); const [loading, setLoading] = React.useState(true); const currentUser = useSelector((state) => state.user.currentUser); + const versionUpdate = useSelector((state) => state.versionUpdate.versionUpdate); const settings = useSelector((state) => state.generalSettings.settings); useEffect(() => { @@ -34,6 +36,7 @@ export default function FredyApp() { await actions.jobs.getProcessingTimes(); await actions.notificationAdapter.getAdapter(); await actions.generalSettings.getGeneralSettings(); + await actions.versionUpdate.getVersionUpdate(); } setLoading(false); } @@ -53,7 +56,6 @@ export default function FredyApp() { } /> ); - return loading ? null : needsLogin() ? ( login() ) : ( @@ -62,7 +64,7 @@ export default function FredyApp() { - + {versionUpdate?.newVersion && } {settings.demoMode && ( <> state.versionUpdate.versionUpdate); + return ( + +

A new version of Fredy is available. Update now to take advantage of the latest features and bug fixes.

+ + {versionUpdate.localFredyVersion} + {versionUpdate.version} + + + {versionUpdate.url} + {' '} + + +

+ + Release Notes + +

+
{stripFullChangelog(versionUpdate.body)}
+ + } + /> + ); + + function stripFullChangelog(text) { + if (text == null) { + return ''; + } + return text.replace(/(?:\r?\n)\*\*Full Changelog\*\*[\s\S]*$/u, ''); + } +} diff --git a/ui/src/components/version/VersionBanner.less b/ui/src/components/version/VersionBanner.less new file mode 100644 index 0000000..8342594 --- /dev/null +++ b/ui/src/components/version/VersionBanner.less @@ -0,0 +1,3 @@ +.versionBanner { + margin-bottom: 1rem; +} \ No newline at end of file diff --git a/ui/src/services/state/store.js b/ui/src/services/state/store.js index 9757857..6706096 100644 --- a/ui/src/services/state/store.js +++ b/ui/src/services/state/store.js @@ -118,6 +118,18 @@ export const useFredyState = create( } }, }, + versionUpdate: { + async getVersionUpdate() { + try { + const response = await xhrGet('/api/version'); + set((state) => ({ + versionUpdate: { ...state.versionUpdate, versionUpdate: response.json }, + })); + } catch (Exception) { + console.error('Error while trying to get resource for api/version. Error:', Exception); + } + }, + }, }; // Initial state @@ -125,6 +137,7 @@ export const useFredyState = create( notificationAdapter: [], generalSettings: { settings: {} }, demoMode: { demoMode: false }, + versionUpdate: {}, provider: [], jobs: { jobs: [], insights: {}, processingTimes: {} }, user: { users: [], currentUser: null }, @@ -135,6 +148,7 @@ export const useFredyState = create( notificationAdapter: { ...effects.notificationAdapter }, generalSettings: { ...effects.generalSettings }, demoMode: { ...effects.demoMode }, + versionUpdate: { ...effects.versionUpdate }, provider: { ...effects.provider }, jobs: { ...effects.jobs }, user: { ...effects.user },