Add app version management, update checks, and periodic SW updates
- Introduced version.json and appVersionAssetPlugin for build-time version tracking. - Enhanced settings page with update check/status UI. - Refactored bootstrap to handle SW updates and initiate periodic checks.
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { APP_VERSION } from '../../app/config.js';
|
||||
|
||||
export function renderSettingsPage() {
|
||||
return `
|
||||
<section class="container-xxl py-4 py-lg-5">
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-lg-7">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-4" x-data="settingsPage()">
|
||||
<div class="card-body p-4" x-data="settingsPage()" x-init="initUpdatePanel()">
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<div>
|
||||
<p class="eyebrow mb-2">Client Settings</p>
|
||||
@@ -40,6 +42,45 @@ export function renderSettingsPage() {
|
||||
</div>
|
||||
<button class="btn btn-primary align-self-start" type="submit">Save settings</button>
|
||||
</form>
|
||||
|
||||
<hr class="my-4" />
|
||||
|
||||
<div class="vstack gap-3">
|
||||
<div>
|
||||
<h2 class="h5 mb-1">App update</h2>
|
||||
<p class="text-body-secondary mb-0">
|
||||
Check for the latest deployed build and force-refresh this installed app when needed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row g-3 small">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="border rounded-3 p-3 h-100 bg-body-tertiary">
|
||||
<div class="text-uppercase text-body-secondary fw-semibold mb-1">Current version</div>
|
||||
<div class="fw-semibold" x-text="update.currentVersion"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="border rounded-3 p-3 h-100 bg-body-tertiary">
|
||||
<div class="text-uppercase text-body-secondary fw-semibold mb-1">Server version</div>
|
||||
<div class="fw-semibold" x-text="update.serverVersion || 'Unavailable'"></div>
|
||||
<template x-if="update.serverBuildTime">
|
||||
<div class="text-body-secondary mt-1" x-text="'Built: ' + formatBuildTime(update.serverBuildTime)"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
<button class="btn btn-outline-secondary" type="button" @click="checkForUpdates()" :disabled="update.isChecking || update.isApplying">
|
||||
<span x-show="!update.isChecking">Check for updates</span>
|
||||
<span x-show="update.isChecking">Checking...</span>
|
||||
</button>
|
||||
<button class="btn btn-primary" type="button" @click="applyUpdate()" :disabled="update.isApplying">
|
||||
<span x-show="!update.isApplying">Update app</span>
|
||||
<span x-show="update.isApplying">Updating...</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="small" :class="updateStatusClass()" x-text="update.statusText"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,6 +109,15 @@ export function settingsPageData(store) {
|
||||
baseUrl: store.config.baseUrl || '',
|
||||
database: store.config.database || '',
|
||||
},
|
||||
update: {
|
||||
currentVersion: APP_VERSION,
|
||||
serverVersion: null,
|
||||
serverBuildTime: null,
|
||||
statusText: 'Ready to check for updates.',
|
||||
statusType: 'secondary',
|
||||
isChecking: false,
|
||||
isApplying: false,
|
||||
},
|
||||
get userLogin() {
|
||||
return store.session?.userLogin || '';
|
||||
},
|
||||
@@ -83,5 +133,83 @@ export function settingsPageData(store) {
|
||||
|
||||
store.addAlert({ type: 'success', message: 'Settings saved locally.' });
|
||||
},
|
||||
formatBuildTime(value) {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return date.toLocaleString();
|
||||
},
|
||||
updateStatusClass() {
|
||||
switch (this.update.statusType) {
|
||||
case 'success':
|
||||
return 'text-success';
|
||||
case 'warning':
|
||||
return 'text-warning';
|
||||
case 'danger':
|
||||
return 'text-danger';
|
||||
default:
|
||||
return 'text-body-secondary';
|
||||
}
|
||||
},
|
||||
async initUpdatePanel() {
|
||||
await this.checkForUpdates();
|
||||
},
|
||||
async checkForUpdates() {
|
||||
if (!window.__loncApp?.checkForAppUpdate) {
|
||||
this.update.statusText = 'Service worker updates are not available in this browser.';
|
||||
this.update.statusType = 'warning';
|
||||
return;
|
||||
}
|
||||
|
||||
this.update.isChecking = true;
|
||||
this.update.statusText = 'Checking for updates...';
|
||||
this.update.statusType = 'secondary';
|
||||
|
||||
try {
|
||||
const result = await window.__loncApp.checkForAppUpdate();
|
||||
this.update.currentVersion = result.currentVersion || APP_VERSION;
|
||||
this.update.serverVersion = result.serverVersion || null;
|
||||
this.update.serverBuildTime = result.serverBuildTime || null;
|
||||
|
||||
if (result.updateAvailable) {
|
||||
this.update.statusText = 'Update available. Use "Update app" to refresh this installed build.';
|
||||
this.update.statusType = 'warning';
|
||||
} else if (result.serverError) {
|
||||
this.update.statusText = `No update signal from server (${result.serverError}).`;
|
||||
this.update.statusType = 'secondary';
|
||||
} else {
|
||||
this.update.statusText = 'This app is up to date.';
|
||||
this.update.statusType = 'success';
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown update check error.';
|
||||
this.update.statusText = `Update check failed: ${message}`;
|
||||
this.update.statusType = 'danger';
|
||||
} finally {
|
||||
this.update.isChecking = false;
|
||||
}
|
||||
},
|
||||
async applyUpdate() {
|
||||
if (!window.__loncApp?.applyAppUpdate) {
|
||||
this.update.statusText = 'Update action is not available in this browser.';
|
||||
this.update.statusType = 'warning';
|
||||
return;
|
||||
}
|
||||
|
||||
this.update.isApplying = true;
|
||||
this.update.statusText = 'Applying update and refreshing app...';
|
||||
this.update.statusType = 'warning';
|
||||
|
||||
try {
|
||||
await window.__loncApp.applyAppUpdate();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown update error.';
|
||||
this.update.statusText = `Update failed: ${message}`;
|
||||
this.update.statusType = 'danger';
|
||||
this.update.isApplying = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user