Files
lonc/src/features/auth/settings-page.js
T

216 lines
8.6 KiB
JavaScript
Raw Normal View History

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()" x-init="initUpdatePanel()">
<div class="d-flex justify-content-between align-items-start mb-4">
<div>
<p class="eyebrow mb-2">Client Settings</p>
<h1 class="h3 mb-1">Connection & workspace</h1>
<p class="text-body-secondary mb-0">
These values are stored locally and reused to start the Tryton user application flow.
</p>
</div>
</div>
<form class="vstack gap-3" @submit.prevent="save()">
<div>
<label class="form-label">Tryton server URL</label>
<input class="form-control" type="url" x-model="form.baseUrl" />
<div class="form-text">
Leave empty to use the same origin as this deployed Lonc frontend.
</div>
</div>
<div>
<label class="form-label">Database name</label>
<input class="form-control" type="text" x-model="form.database" required />
</div>
<div>
<label class="form-label">User login</label>
<input class="form-control" type="text" :value="userLogin" readonly />
</div>
<div>
<label class="form-label">Application key</label>
<input class="form-control font-monospace" type="text" :value="maskedApplicationKey" readonly />
<div class="form-text">
Only the first 16 characters are shown for identification.
</div>
</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>
<div class="col-12 col-lg-5">
<div class="card border-0 shadow-sm">
<div class="card-body p-4">
<h2 class="h5">Integration notes</h2>
<ul class="text-body-secondary small ps-3 mb-0">
<li>Connection uses Tryton user application keys for the <code>kitchen</code> application.</li>
<li>Leaving the server URL empty makes API calls use same-origin relative paths.</li>
<li>Kitchen-scoped requests are built as <code>/{database}/kitchen/{kitchenId}/...</code>.</li>
<li>Label preview accepts image blobs, image URLs, or SVG payloads.</li>
</ul>
</div>
</div>
</div>
</div>
</section>
`;
}
export function settingsPageData(store) {
return {
form: {
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 || '';
},
get maskedApplicationKey() {
const key = store.session?.applicationKey || '';
return key ? `${key.slice(0, 16)}...` : '';
},
save() {
store.setConfig({
baseUrl: this.form.baseUrl.trim(),
database: this.form.database.trim(),
});
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;
}
},
};
}