Upload via GUI (72 Dateien)

This commit is contained in:
2026-10-25 17:48:07 +00:00
parent e812dac9e4
commit 7b6403ceb0
7 changed files with 83 additions and 3 deletions
+25
View File
@@ -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
+43
View File
@@ -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.' } };
+3
View File
@@ -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()),
});