Upload via GUI (72 Dateien)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "deskhub",
|
||||
"productName": "DeskHub",
|
||||
"version": "2.9.1",
|
||||
"version": "2.10.2",
|
||||
"description": "DeskHub Desktop-Client für Mitarbeiter",
|
||||
"main": "src/main.js",
|
||||
"author": "M_Viper",
|
||||
|
||||
@@ -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()),
|
||||
});
|
||||
|
||||
@@ -66,7 +66,8 @@ window.deskhub = {
|
||||
login: async () => ({ staff }), logout: async () => true, notify: async () => {}, badge: async () => {},
|
||||
openWeb: async (path) => { window.__web = path; return { data: true }; },
|
||||
api: async (req) => { window.__calls.push(req.path); if (req.body) (window.__bodies = window.__bodies || {})[req.path] = req.body; window.__bodies = window.__bodies || {}; const f = routes[req.path]; return f ? { data: f(req) } : { error: { message: 'Mock: ' + req.path + ' nicht vorhanden' } }; },
|
||||
onSessionExpired() {}, onNavigate() {},
|
||||
onSessionExpired() {}, onNavigate() {}, onUpdateProgress() {},
|
||||
updateCheck: async () => ({ data: window.__update || { available: false, packaged: true, current: '1.1.0' } }), updateInstall: async () => ({ data: true }),
|
||||
};
|
||||
</script>
|
||||
<script src="../src/lang-en.js"></script><script src="../src/i18n.js"></script><script src="../src/app.js"></script><script src="../src/admin-extra.js"></script><script src="../src/admin-status.js"></script>
|
||||
|
||||
@@ -37,6 +37,14 @@ app.whenReady().then(async () => {
|
||||
await run(`go('dashboard')`);
|
||||
check('Dashboard zeigt Kennzahlen', await waitFor(`document.getElementById('main').innerText.includes('Drucker druckt nicht')`));
|
||||
|
||||
console.log('Programm-Update');
|
||||
check('Ohne neue Version kein Update-Banner', await run(`!document.getElementById('banners').innerText.includes('Update verfügbar')`));
|
||||
await run(`window.__update = { available: true, packaged: true, version: '9.9.9', current: '1.1.0', notes: '' }; checkUpdate()`);
|
||||
check('Neue Version zeigt Banner mit Button', await waitFor(`document.getElementById('banners').innerText.includes('Update verfügbar: DeskHub 9.9.9') && document.getElementById('banners').innerText.includes('Jetzt aktualisieren')`));
|
||||
await run(`[...document.querySelectorAll('#banners button')].find(b => b.textContent === 'Später').click()`);
|
||||
check('„Später“ blendet das Banner aus', await run(`!document.getElementById('banners').innerText.includes('Update verfügbar')`));
|
||||
await run(`window.__update = undefined; state.update = undefined; paintBanners()`);
|
||||
|
||||
await run(`go('tickets')`);
|
||||
check('Ticketliste zeigt alle Tickets', await waitFor(`['Drucker druckt nicht','Passwort zurücksetzen','Rechnung fehlt','VPN Verbindung bricht ab'].every(t => document.getElementById('main').innerText.includes(t))`));
|
||||
check('Reiter „Wiedervorlage“, „Pausiert“ und „Beobachtet“ sind da', await run(`(() => { const t = [...document.querySelectorAll('#main .tabs button')].map((b) => b.innerText).join('|'); return t.includes('Wiedervorlage') && t.includes('Pausiert') && t.includes('Beobachtet'); })()`));
|
||||
|
||||
Reference in New Issue
Block a user