mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
adding version banner to check if a new version of fredy is available
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":null,"sqlitepath":"/db"}
|
{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":true,"sqlitepath":"/db"}
|
||||||
@@ -7,6 +7,7 @@ import { loginRouter } from './routes/loginRoute.js';
|
|||||||
import { config } from '../utils.js';
|
import { config } from '../utils.js';
|
||||||
import { userRouter } from './routes/userRoute.js';
|
import { userRouter } from './routes/userRoute.js';
|
||||||
import { jobRouter } from './routes/jobRouter.js';
|
import { jobRouter } from './routes/jobRouter.js';
|
||||||
|
import { versionRouter } from './routes/versionRouter.js';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import restana from 'restana';
|
import restana from 'restana';
|
||||||
import files from 'serve-static';
|
import files from 'serve-static';
|
||||||
@@ -23,6 +24,7 @@ service.use(cookieSession());
|
|||||||
service.use(staticService);
|
service.use(staticService);
|
||||||
service.use('/api/admin', authInterceptor());
|
service.use('/api/admin', authInterceptor());
|
||||||
service.use('/api/jobs', authInterceptor());
|
service.use('/api/jobs', authInterceptor());
|
||||||
|
service.use('/api/version', authInterceptor());
|
||||||
// /admin can only be accessed when user is having admin permissions
|
// /admin can only be accessed when user is having admin permissions
|
||||||
service.use('/api/admin', adminInterceptor());
|
service.use('/api/admin', adminInterceptor());
|
||||||
service.use('/api/jobs/notificationAdapter', notificationAdapterRouter);
|
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/provider', providerRouter);
|
||||||
service.use('/api/jobs/insights', analyticsRouter);
|
service.use('/api/jobs/insights', analyticsRouter);
|
||||||
service.use('/api/admin/users', userRouter);
|
service.use('/api/admin/users', userRouter);
|
||||||
|
service.use('/api/version', versionRouter);
|
||||||
service.use('/api/jobs', jobRouter);
|
service.use('/api/jobs', jobRouter);
|
||||||
service.use('/api/login', loginRouter);
|
service.use('/api/login', loginRouter);
|
||||||
//this route is unsecured intentionally as it is being queried from the login page
|
//this route is unsecured intentionally as it is being queried from the login page
|
||||||
|
|||||||
30
lib/api/routes/versionRouter.js
Normal file
30
lib/api/routes/versionRouter.js
Normal file
@@ -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 };
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import { getJobs } from '../storage/jobStorage.js';
|
import { getJobs } from '../storage/jobStorage.js';
|
||||||
import { getUniqueId } from './uniqueId.js';
|
import { getUniqueId } from './uniqueId.js';
|
||||||
import { config, inDevMode } from '../../utils.js';
|
import { config, getPackageVersion, inDevMode } from '../../utils.js';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
import { packageUp } from 'package-up';
|
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import logger from '../logger.js';
|
import logger from '../logger.js';
|
||||||
|
|
||||||
@@ -77,15 +75,3 @@ function enrichTrackingObject(trackingObject) {
|
|||||||
version,
|
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';
|
|
||||||
}
|
|
||||||
|
|||||||
20
lib/utils.js
20
lib/utils.js
@@ -3,8 +3,9 @@ import { fileURLToPath } from 'node:url';
|
|||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { DEFAULT_CONFIG } from './defaultConfig.js';
|
import { DEFAULT_CONFIG } from './defaultConfig.js';
|
||||||
import fs from 'fs';
|
import fs, { readFileSync } from 'fs';
|
||||||
import logger from './services/logger.js';
|
import logger from './services/logger.js';
|
||||||
|
import { packageUp } from 'package-up';
|
||||||
|
|
||||||
const RE_GT = />/g;
|
const RE_GT = />/g;
|
||||||
const RE_WEBP = /\/format\/webp/gi;
|
const RE_WEBP = /\/format\/webp/gi;
|
||||||
@@ -196,6 +197,22 @@ const normalizeImageUrl = (url) => {
|
|||||||
return u;
|
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();
|
await refreshConfig();
|
||||||
|
|
||||||
export { isOneOf };
|
export { isOneOf };
|
||||||
@@ -206,6 +223,7 @@ export { duringWorkingHoursOrNotSet };
|
|||||||
export { getDirName };
|
export { getDirName };
|
||||||
export { config };
|
export { config };
|
||||||
export { buildHash };
|
export { buildHash };
|
||||||
|
export { getPackageVersion };
|
||||||
export default {
|
export default {
|
||||||
isOneOf,
|
isOneOf,
|
||||||
nullOrEmpty,
|
nullOrEmpty,
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ import Jobs from './views/jobs/Jobs';
|
|||||||
import './App.less';
|
import './App.less';
|
||||||
import TrackingModal from './components/tracking/TrackingModal.jsx';
|
import TrackingModal from './components/tracking/TrackingModal.jsx';
|
||||||
import { Banner } from '@douyinfe/semi-ui';
|
import { Banner } from '@douyinfe/semi-ui';
|
||||||
|
import VersionBanner from './components/version/VersionBanner.jsx';
|
||||||
|
|
||||||
export default function FredyApp() {
|
export default function FredyApp() {
|
||||||
const actions = useActions();
|
const actions = useActions();
|
||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = React.useState(true);
|
||||||
const currentUser = useSelector((state) => state.user.currentUser);
|
const currentUser = useSelector((state) => state.user.currentUser);
|
||||||
|
const versionUpdate = useSelector((state) => state.versionUpdate.versionUpdate);
|
||||||
const settings = useSelector((state) => state.generalSettings.settings);
|
const settings = useSelector((state) => state.generalSettings.settings);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -34,6 +36,7 @@ export default function FredyApp() {
|
|||||||
await actions.jobs.getProcessingTimes();
|
await actions.jobs.getProcessingTimes();
|
||||||
await actions.notificationAdapter.getAdapter();
|
await actions.notificationAdapter.getAdapter();
|
||||||
await actions.generalSettings.getGeneralSettings();
|
await actions.generalSettings.getGeneralSettings();
|
||||||
|
await actions.versionUpdate.getVersionUpdate();
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -53,7 +56,6 @@ export default function FredyApp() {
|
|||||||
<Route path="*" element={<Navigate to="/login" replace />} />
|
<Route path="*" element={<Navigate to="/login" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
|
||||||
return loading ? null : needsLogin() ? (
|
return loading ? null : needsLogin() ? (
|
||||||
login()
|
login()
|
||||||
) : (
|
) : (
|
||||||
@@ -62,7 +64,7 @@ export default function FredyApp() {
|
|||||||
<Logout />
|
<Logout />
|
||||||
<Logo width={190} white />
|
<Logo width={190} white />
|
||||||
<Menu isAdmin={isAdmin()} />
|
<Menu isAdmin={isAdmin()} />
|
||||||
|
{versionUpdate?.newVersion && <VersionBanner />}
|
||||||
{settings.demoMode && (
|
{settings.demoMode && (
|
||||||
<>
|
<>
|
||||||
<Banner
|
<Banner
|
||||||
|
|||||||
43
ui/src/components/version/VersionBanner.jsx
Normal file
43
ui/src/components/version/VersionBanner.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Banner, Descriptions } from '@douyinfe/semi-ui';
|
||||||
|
import { useSelector } from '../../services/state/store.js';
|
||||||
|
|
||||||
|
import './VersionBanner.less';
|
||||||
|
|
||||||
|
export default function VersionBanner() {
|
||||||
|
const versionUpdate = useSelector((state) => state.versionUpdate.versionUpdate);
|
||||||
|
return (
|
||||||
|
<Banner
|
||||||
|
className="versionBanner"
|
||||||
|
type="success"
|
||||||
|
icon={null}
|
||||||
|
description={
|
||||||
|
<div>
|
||||||
|
<p>A new version of Fredy is available. Update now to take advantage of the latest features and bug fixes.</p>
|
||||||
|
<Descriptions row size="small">
|
||||||
|
<Descriptions.Item itemKey="Your Version">{versionUpdate.localFredyVersion}</Descriptions.Item>
|
||||||
|
<Descriptions.Item itemKey="Latest Version">{versionUpdate.version}</Descriptions.Item>
|
||||||
|
<Descriptions.Item itemKey="Github Release">
|
||||||
|
<a href={versionUpdate.url} target="_blank" rel="noreferrer">
|
||||||
|
{versionUpdate.url}
|
||||||
|
</a>{' '}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
<small>Release Notes</small>
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
<pre>{stripFullChangelog(versionUpdate.body)}</pre>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
function stripFullChangelog(text) {
|
||||||
|
if (text == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return text.replace(/(?:\r?\n)\*\*Full Changelog\*\*[\s\S]*$/u, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
3
ui/src/components/version/VersionBanner.less
Normal file
3
ui/src/components/version/VersionBanner.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.versionBanner {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
@@ -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
|
// Initial state
|
||||||
@@ -125,6 +137,7 @@ export const useFredyState = create(
|
|||||||
notificationAdapter: [],
|
notificationAdapter: [],
|
||||||
generalSettings: { settings: {} },
|
generalSettings: { settings: {} },
|
||||||
demoMode: { demoMode: false },
|
demoMode: { demoMode: false },
|
||||||
|
versionUpdate: {},
|
||||||
provider: [],
|
provider: [],
|
||||||
jobs: { jobs: [], insights: {}, processingTimes: {} },
|
jobs: { jobs: [], insights: {}, processingTimes: {} },
|
||||||
user: { users: [], currentUser: null },
|
user: { users: [], currentUser: null },
|
||||||
@@ -135,6 +148,7 @@ export const useFredyState = create(
|
|||||||
notificationAdapter: { ...effects.notificationAdapter },
|
notificationAdapter: { ...effects.notificationAdapter },
|
||||||
generalSettings: { ...effects.generalSettings },
|
generalSettings: { ...effects.generalSettings },
|
||||||
demoMode: { ...effects.demoMode },
|
demoMode: { ...effects.demoMode },
|
||||||
|
versionUpdate: { ...effects.versionUpdate },
|
||||||
provider: { ...effects.provider },
|
provider: { ...effects.provider },
|
||||||
jobs: { ...effects.jobs },
|
jobs: { ...effects.jobs },
|
||||||
user: { ...effects.user },
|
user: { ...effects.user },
|
||||||
|
|||||||
Reference in New Issue
Block a user