+
+ {#if newVersionInfo}
+
+
+
+ {$t('app.components.footer.new_version_available')}
+
+
+
+ {newVersionInfo.description}
+
+
+
+ {/if}
© {new Date().getFullYear()}
Open Archiver. {$t(
'app.components.footer.all_rights_reserved'
)}
+
+ Version: {currentVersion}
+
diff --git a/packages/frontend/src/lib/translations/en.json b/packages/frontend/src/lib/translations/en.json
index f8d98b2..2e44dee 100644
--- a/packages/frontend/src/lib/translations/en.json
+++ b/packages/frontend/src/lib/translations/en.json
@@ -163,7 +163,8 @@
"not_available": "Raw .eml file not available for this email."
},
"footer": {
- "all_rights_reserved": "All rights reserved."
+ "all_rights_reserved": "All rights reserved.",
+ "new_version_available": "New version available"
},
"ingestion_source_form": {
"provider_generic_imap": "Generic IMAP",
diff --git a/packages/frontend/src/routes/+layout.server.ts b/packages/frontend/src/routes/+layout.server.ts
index 77de0f6..c0dfb31 100644
--- a/packages/frontend/src/routes/+layout.server.ts
+++ b/packages/frontend/src/routes/+layout.server.ts
@@ -3,6 +3,11 @@ import type { LayoutServerLoad } from './$types';
import 'dotenv/config';
import { api } from '$lib/server/api';
import type { SystemSettings } from '@open-archiver/types';
+import { version } from '../../../../package.json';
+import semver from 'semver';
+
+let newVersionInfo: { version: string; description: string; url: string } | null = null;
+let lastChecked: Date | null = null;
export const load: LayoutServerLoad = async (event) => {
const { locals, url } = event;
@@ -32,10 +37,33 @@ export const load: LayoutServerLoad = async (event) => {
? await systemSettingsResponse.json()
: null;
+ const now = new Date();
+ if (!lastChecked || now.getTime() - lastChecked.getTime() > 1000 * 60 * 60) {
+ try {
+ const res = await fetch('https://api.github.com/repos/LogicLabs-OU/OpenArchiver/releases/latest');
+ if (res.ok) {
+ const latestRelease = await res.json();
+ const latestVersion = latestRelease.tag_name.replace('v', '');
+ if (semver.gt(latestVersion, version)) {
+ newVersionInfo = {
+ version: latestVersion,
+ description: latestRelease.name,
+ url: latestRelease.html_url
+ };
+ }
+ }
+ lastChecked = now;
+ } catch (error) {
+ console.error('Failed to fetch latest version from GitHub:', error);
+ }
+ }
+
return {
user: locals.user,
accessToken: locals.accessToken,
isDemo: process.env.IS_DEMO === 'true',
systemSettings,
+ currentVersion: version,
+ newVersionInfo: newVersionInfo
};
};
diff --git a/packages/frontend/src/routes/+layout.svelte b/packages/frontend/src/routes/+layout.svelte
index 80db5ae..8bc717a 100644
--- a/packages/frontend/src/routes/+layout.svelte
+++ b/packages/frontend/src/routes/+layout.svelte
@@ -35,5 +35,5 @@
{@render children()}
-
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3dbb1fd..755020f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -229,6 +229,9 @@ importers:
postal-mime:
specifier: ^2.4.4
version: 2.4.4
+ semver:
+ specifier: ^7.7.2
+ version: 7.7.2
svelte-persisted-store:
specifier: ^0.12.0
version: 0.12.0(svelte@5.35.5)
@@ -263,6 +266,9 @@ importers:
'@types/d3-shape':
specifier: ^3.1.7
version: 3.1.7
+ '@types/semver':
+ specifier: ^7.7.1
+ version: 7.7.1
dotenv:
specifier: ^17.2.0
version: 17.2.0
@@ -1802,6 +1808,9 @@ packages:
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
+ '@types/semver@7.7.1':
+ resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
+
'@types/send@0.17.5':
resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==}
@@ -6557,6 +6566,8 @@ snapshots:
'@types/resolve@1.20.2': {}
+ '@types/semver@7.7.1': {}
+
'@types/send@0.17.5':
dependencies:
'@types/mime': 1.3.5