Upload via GUI (72 Dateien)
This commit is contained in:
@@ -67,9 +67,33 @@ function paintBanners() {
|
||||
box.append(h('div', { class: 'banner error' }, h('span', { class: 'grow' }, `Keine Verbindung zum Server – nächster Versuch in ${state.retryIn || 10} s. Bereits geladene Daten sind eventuell veraltet.`),
|
||||
h('button', { class: 'btn ghost sm', onclick: retryNow }, 'Jetzt erneut versuchen')));
|
||||
}
|
||||
const u = state.update;
|
||||
if (u?.available) {
|
||||
box.append(h('div', { class: 'banner warn' },
|
||||
h('span', { class: 'grow' }, u.busy ? `Update ${u.version} wird geladen … ${u.pct || 0} %` : `Update verfügbar: DeskHub ${u.version} (installiert: ${u.current}).${u.notes ? ' ' + u.notes : ''}`),
|
||||
u.busy ? null : h('button', { class: 'btn sm', onclick: installUpdate }, 'Jetzt aktualisieren'),
|
||||
u.busy ? null : h('button', { class: 'btn ghost sm', onclick: () => { state.update = { ...u, dismissed: u.version, available: false }; paintBanners(); } }, 'Später')));
|
||||
}
|
||||
for (const i of state.health || []) box.append(h('div', { class: 'banner ' + (i.level === 'error' ? 'error' : 'warn') }, h('span', { class: 'grow' }, (i.level === 'error' ? 'Störung: ' : 'Hinweis: ') + i.message)));
|
||||
}
|
||||
const post = (path, body) => api(path, { method: 'POST', body });
|
||||
|
||||
// ─── Programm-Update: beim Start und danach alle 6 Stunden prüfen, Banner mit Button ───────────────────────
|
||||
async function checkUpdate() {
|
||||
try {
|
||||
const r = await window.deskhub.updateCheck();
|
||||
if (r.error || !r.data?.packaged || state.update?.busy) return;
|
||||
const d = r.data;
|
||||
state.update = { ...d, available: d.available && state.update?.dismissed !== d.version, dismissed: state.update?.dismissed };
|
||||
paintBanners();
|
||||
} catch { /* Update-Prüfung ist optional */ }
|
||||
}
|
||||
async function installUpdate() {
|
||||
state.update = { ...state.update, busy: true, pct: 0 }; paintBanners();
|
||||
const r = await window.deskhub.updateInstall();
|
||||
if (r.error) { state.update = { ...state.update, busy: false }; paintBanners(); toast(r.error.message, true); }
|
||||
}
|
||||
window.deskhub.onUpdateProgress((pct) => { if (state.update?.busy) { state.update.pct = pct; paintBanners(); } });
|
||||
async function run(fn, okMsg) {
|
||||
try { const r = await fn(); if (okMsg) toast(okMsg); return r; }
|
||||
catch (e) { toast(e.message, true); return undefined; }
|
||||
@@ -145,6 +169,7 @@ const initials = (name) => (name || '?').split(/\s+/).filter(Boolean).slice(0, 2
|
||||
const metaKey = (url) => 'meta:' + url;
|
||||
async function startApp(staff, cachedMeta, serverUrl) {
|
||||
state.staff = staff;
|
||||
checkUpdate(); clearInterval(state.updateTimer); state.updateTimer = setInterval(checkUpdate, 6 * 3600 * 1000);
|
||||
state.since = ''; state.badge = 0;
|
||||
if (!state.prefs) state.prefs = await window.deskhub.settings();
|
||||
applyTheme();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -88,6 +88,49 @@ ipcMain.handle('api', async (_e, req) => {
|
||||
} catch (e) { return { error: { status: e.status, code: e.code, message: e.message || String(e) } }; }
|
||||
});
|
||||
|
||||
// ─── Programm-Update (Version und Setup kommen vom DeskHub-Server, Quelle ist die Plugin-Webseite) ──────────
|
||||
const verParts = (v) => String(v).split(/[-+]/)[0].split('.').map((n) => parseInt(n, 10) || 0);
|
||||
function isNewer(remote, local) {
|
||||
const a = verParts(remote), b = verParts(local);
|
||||
for (let i = 0; i < Math.max(a.length, b.length); i++) { if ((a[i] || 0) !== (b[i] || 0)) return (a[i] || 0) > (b[i] || 0); }
|
||||
return false;
|
||||
}
|
||||
let pendingUpdate = null;
|
||||
ipcMain.handle('update:check', async () => {
|
||||
try {
|
||||
const r = await callApi({ path: 'app_update.php', query: { platform: 'windows' } });
|
||||
const d = r.data;
|
||||
if (!d?.available || !d.version) return { data: { available: false, current: app.getVersion() } };
|
||||
pendingUpdate = d;
|
||||
return { data: { available: isNewer(d.version, app.getVersion()), version: d.version, notes: d.notes || '', size: d.size || 0, current: app.getVersion(), packaged: app.isPackaged } };
|
||||
} catch (e) { return { error: { message: e.message || String(e) } }; }
|
||||
});
|
||||
ipcMain.handle('update:install', async () => {
|
||||
if (!pendingUpdate || !session) return { error: { message: 'Keine Update-Information vorhanden.' } };
|
||||
try {
|
||||
const url = new URL(`${session.serverUrl}/api/staff/v1/app_update.php`);
|
||||
url.searchParams.set('platform', 'windows'); url.searchParams.set('download', '1');
|
||||
const res = await fetch(url, { headers: { Authorization: `Bearer ${session.token}`, 'X-DeskHub-Authorization': `Bearer ${session.token}` } });
|
||||
if (!res.ok) return { error: { message: `Download fehlgeschlagen (${res.status}).` } };
|
||||
const dir = fs.mkdtempSync(path.join(app.getPath('temp'), 'deskhub-update-'));
|
||||
const file = path.join(dir, 'DeskHub-Setup.exe');
|
||||
const out = fs.createWriteStream(file);
|
||||
const hash = require('crypto').createHash('sha256');
|
||||
const total = Number(res.headers.get('content-length')) || pendingUpdate.size || 0;
|
||||
let got = 0;
|
||||
for await (const chunk of res.body) {
|
||||
hash.update(chunk); got += chunk.length;
|
||||
if (!out.write(chunk)) await new Promise((ok) => out.once('drain', ok));
|
||||
win?.webContents.send('update-progress', total ? Math.min(99, Math.round((got / total) * 100)) : 0);
|
||||
}
|
||||
await new Promise((ok, fail) => out.end((err) => (err ? fail(err) : ok())));
|
||||
if (hash.digest('hex') !== String(pendingUpdate.sha256).toLowerCase()) { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} return { error: { message: 'Die heruntergeladene Datei ist beschädigt (Prüfsumme stimmt nicht). Bitte erneut versuchen.' } }; }
|
||||
require('child_process').spawn(file, [], { detached: true, stdio: 'ignore' }).unref();
|
||||
setTimeout(() => { quitting = true; app.quit(); }, 500);
|
||||
return { data: true };
|
||||
} catch (e) { return { error: { message: e.message || String(e) } }; }
|
||||
});
|
||||
|
||||
// Seite der Weboberfläche im Standardbrowser öffnen: Der Server gibt eine einmalige Anmelde-Adresse (und prüft dabei die Rechte)
|
||||
ipcMain.handle('web:open', async (_e, { path: p }) => {
|
||||
if (typeof p !== 'string' || !p.startsWith('/staff/') || p.length > 200) return { error: { message: 'Ungültige Seite.' } };
|
||||
|
||||
@@ -10,6 +10,9 @@ contextBridge.exposeInMainWorld('deskhub', {
|
||||
settings: () => ipcRenderer.invoke('settings:get'),
|
||||
setSettings: (patch) => ipcRenderer.invoke('settings:set', patch),
|
||||
badge: (count, dataUrl) => ipcRenderer.invoke('badge', { count, dataUrl }),
|
||||
updateCheck: () => ipcRenderer.invoke('update:check'),
|
||||
updateInstall: () => ipcRenderer.invoke('update:install'),
|
||||
onUpdateProgress: (cb) => ipcRenderer.on('update-progress', (_e, pct) => cb(pct)),
|
||||
onNavigate: (cb) => ipcRenderer.on('navigate', (_e, page) => cb(page)),
|
||||
onSessionExpired: (cb) => ipcRenderer.on('session-expired', () => cb()),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user