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() {
A new version of Fredy is available. Update now to take advantage of the latest features and bug fixes.
++ + 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 },