From 8324357edbb41b412d49cb84d30c867bbba641ee Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 26 Sep 2025 10:45:55 +0200 Subject: [PATCH] Improvements (#193) * improving release banner * renaming general to settings * fixing working hours if they go to next day * fixing comparing versions * upgrade dependencies --- README.md | 2 - lib/api/routes/versionRouter.js | 3 +- lib/utils.js | 48 +++++++++++++++------ package.json | 5 ++- test/provider/utils.test.js | 15 +++++++ ui/src/components/menu/Menu.jsx | 2 +- ui/src/components/version/VersionBanner.jsx | 12 ++---- yarn.lock | 44 +++++++++---------- 8 files changed, 82 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 9e47046..9275f0b 100755 --- a/README.md +++ b/README.md @@ -28,8 +28,6 @@ With a modern architecture, Fredy provides a **clean Web UI**, removes duplicates across platforms, and stores results so you never see the same listing twice. - - ------------------------------------------------------------------------ ## ✨ Key Features diff --git a/lib/api/routes/versionRouter.js b/lib/api/routes/versionRouter.js index 1db98a3..4a685e6 100644 --- a/lib/api/routes/versionRouter.js +++ b/lib/api/routes/versionRouter.js @@ -1,6 +1,7 @@ import restana from 'restana'; import fetch from 'node-fetch'; import { getPackageVersion } from '../../utils.js'; +import semver from 'semver'; const service = restana(); const versionRouter = service.newRouter(); @@ -15,7 +16,7 @@ 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) { + if (data.tag_name == null || semver.gte(localFredyVersion, data.tag_name)) { return null; } return { diff --git a/lib/utils.js b/lib/utils.js index 7f9c3bd..e440796 100755 --- a/lib/utils.js +++ b/lib/utils.js @@ -109,11 +109,22 @@ function timeStringToMs(timeString, now) { } /** - * Check whether current time is within configured working hours, or no hours are set. - * If working hours are missing or incomplete, returns true. - * @param {{workingHours?: {from?: string, to?: string}}} config - * @param {number} now - Epoch ms - * @returns {boolean} + * Determine whether the given timestamp is within the configured working hours, or return true when the window is not set. + * - If workingHours is missing or either 'from' or 'to' is empty/null, returns true. + * - Supports windows that cross midnight (e.g., from '23:00' to '06:00'). + * + * Time parsing is based on the local timezone of the running process. + * + * @param {{workingHours?: {from?: string|null, to?: string|null}}} config - Configuration object containing working hours in 'HH:mm' format. + * @param {number} now - Epoch milliseconds to evaluate. + * @returns {boolean} True when execution is allowed at 'now'. + * @example + * // Same-day window + * duringWorkingHoursOrNotSet({ workingHours: { from: '08:00', to: '17:00' } }, someTime); + * @example + * // Window crossing midnight + * // For { from: '05:00', to: '00:30' } → 23:00 => true, 01:00 => false, 06:00 => true + * duringWorkingHoursOrNotSet({ workingHours: { from: '05:00', to: '00:30' } }, Date.now()); */ function duringWorkingHoursOrNotSet(config, now) { const { workingHours } = config; @@ -122,7 +133,20 @@ function duringWorkingHoursOrNotSet(config, now) { } const toDate = timeStringToMs(workingHours.to, now); const fromDate = timeStringToMs(workingHours.from, now); - return fromDate <= now && toDate >= now; + + // If parsing fails (e.g., malformed time), be lenient and allow. + if (isNaN(toDate) || isNaN(fromDate)) { + return true; + } + + if (toDate >= fromDate) { + // Same-day window (e.g., 08:00 - 17:00) + return now >= fromDate && now <= toDate; + } + + // Window crosses midnight (e.g., 05:00 -> 00:30 next day) + // Accept if we are after 'from' today OR before 'to' today (which represents next day's cutoff). + return now >= fromDate || now <= toDate; } /** @@ -244,13 +268,13 @@ function sleep(ms) { } /** - * returns a random into between start and end - * @param a start int - * @param b max int - * @returns {*} + * Return a random integer between min and max (inclusive). + * @param {number} min - Minimum integer value. + * @param {number} max - Maximum integer value. + * @returns {number} A random integer N where min <= N <= max. */ -function randomBetween(a, b) { - return Math.floor(Math.random() * (b - a + 1)) + a; +function randomBetween(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; } // Call refreshConfig() from the application entrypoint during startup to populate config. diff --git a/package.json b/package.json index f1e94f9..f6749d7 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "12.2.0", + "version": "12.2.1", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", @@ -85,6 +85,7 @@ "react-router": "7.9.2", "react-router-dom": "7.9.2", "restana": "5.1.0", + "semver": "^7.7.2", "serve-static": "2.2.0", "slack": "11.0.2", "vite": "7.1.7", @@ -104,7 +105,7 @@ "history": "5.3.0", "husky": "9.1.7", "less": "4.4.1", - "lint-staged": "16.2.0", + "lint-staged": "16.2.1", "mocha": "11.7.2", "nodemon": "^3.1.10", "prettier": "3.6.2" diff --git a/test/provider/utils.test.js b/test/provider/utils.test.js index 8dc9fb3..15fb16c 100644 --- a/test/provider/utils.test.js +++ b/test/provider/utils.test.js @@ -8,6 +8,7 @@ const fakeWorkingHoursConfig = (from, to) => ({ from, }, }); + describe('utils', () => { describe('#isOneOf()', () => { it('should be false', () => { @@ -33,5 +34,19 @@ describe('utils', () => { it('should be true if only from is set', () => { expect(duringWorkingHoursOrNotSet(fakeWorkingHoursConfig('12:00', null), 1622026740000)).to.be.true; }); + it('should handle working hours that cross midnight (e.g., 05:00 → 00:30)', () => { + const cfg = fakeWorkingHoursConfig('05:00', '00:30'); + const mkTs = (h, m = 0) => { + const d = new Date(); + d.setHours(h); + d.setMinutes(m); + d.setSeconds(0); + d.setMilliseconds(0); + return d.getTime(); + }; + expect(duringWorkingHoursOrNotSet(cfg, mkTs(23, 0))).to.be.true; // 23:00 => within window + expect(duringWorkingHoursOrNotSet(cfg, mkTs(1, 0))).to.be.false; // 01:00 => outside window + expect(duringWorkingHoursOrNotSet(cfg, mkTs(6, 0))).to.be.true; // 06:00 => within window + }); }); }); diff --git a/ui/src/components/menu/Menu.jsx b/ui/src/components/menu/Menu.jsx index f49f9a8..45683ae 100644 --- a/ui/src/components/menu/Menu.jsx +++ b/ui/src/components/menu/Menu.jsx @@ -53,7 +53,7 @@ const TopMenu = function TopMenu({ isAdmin }) { tab={ - General + Settings } /> diff --git a/ui/src/components/version/VersionBanner.jsx b/ui/src/components/version/VersionBanner.jsx index 24e8e72..4036c0a 100644 --- a/ui/src/components/version/VersionBanner.jsx +++ b/ui/src/components/version/VersionBanner.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { Banner, Descriptions } from '@douyinfe/semi-ui'; import { useSelector } from '../../services/state/store.js'; +import { MarkdownRender } from '@douyinfe/semi-ui'; import './VersionBanner.less'; @@ -12,7 +13,7 @@ export default function VersionBanner() { type="success" icon={null} description={ -
+

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

{versionUpdate.localFredyVersion} @@ -28,16 +29,9 @@ export default function VersionBanner() { 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/yarn.lock b/yarn.lock index 631f3a8..a47b6ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2538,16 +2538,16 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -commander@14.0.1: - version "14.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.1.tgz#2f9225c19e6ebd0dc4404dd45821b2caa17ea09b" - integrity sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A== - commander@2: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.1.tgz#2f9225c19e6ebd0dc4404dd45821b2caa17ea09b" + integrity sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A== + compute-scroll-into-view@^1.0.20: version "1.0.20" resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz#1768b5522d1172754f5d0c9b02de3af6be506a43" @@ -4559,20 +4559,20 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -lint-staged@16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-16.2.0.tgz#ea7157bf007bdb50d2bb0559bc91c8e77d71c84b" - integrity sha512-spdYSOCQ2MdZ9CM1/bu/kDmaYGsrpNOeu1InFFV8uhv14x6YIubGxbCpSmGILFoxkiheNQPDXSg5Sbb5ZuVnug== +lint-staged@16.2.1: + version "16.2.1" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-16.2.1.tgz#bb82da8ce10059296b220f321980f0ee1ce40c28" + integrity sha512-KMeYmH9wKvHsXdUp+z6w7HN3fHKHXwT1pSTQTYxB9kI6ekK1rlL3kLZEoXZCppRPXFK9PFW/wfQctV7XUqMrPQ== dependencies: - commander "14.0.1" - listr2 "9.0.4" - micromatch "4.0.8" - nano-spawn "1.0.3" - pidtree "0.6.0" - string-argv "0.3.2" - yaml "2.8.1" + commander "^14.0.1" + listr2 "^9.0.4" + micromatch "^4.0.8" + nano-spawn "^1.0.3" + pidtree "^0.6.0" + string-argv "^0.3.2" + yaml "^2.8.1" -listr2@9.0.4: +listr2@^9.0.4: version "9.0.4" resolved "https://registry.yarnpkg.com/listr2/-/listr2-9.0.4.tgz#2916e633ae6e09d1a3f981172937ac1c5a8fa64f" integrity sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ== @@ -5271,7 +5271,7 @@ micromark@^4.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromatch@4.0.8: +micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -5401,7 +5401,7 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nano-spawn@1.0.3: +nano-spawn@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.3.tgz#ef8d89a275eebc8657e67b95fc312a6527a05b8d" integrity sha512-jtpsQDetTnvS2Ts1fiRdci5rx0VYws5jGyC+4IYOTnIQ/wwdf6JdomlHBwqC3bJYOvaKu0C2GSZ1A60anrYpaA== @@ -5817,7 +5817,7 @@ picomatch@^4.0.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== -pidtree@0.6.0: +pidtree@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== @@ -6835,7 +6835,7 @@ streamx@^2.15.0, streamx@^2.21.0: optionalDependencies: bare-events "^2.2.0" -string-argv@0.3.2: +string-argv@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== @@ -7583,7 +7583,7 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yaml@2.8.1: +yaml@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==