Upload via GUI (72 Dateien)
This commit is contained in:
+19
-2
@@ -22,10 +22,27 @@ const routes = {
|
||||
return { dbReady: true, settings: { enabled: true, mode: 'offline_only', name: '', greeting: '', noAnswer: '', departmentId: 0, priorityId: 0, logDays: 90, synonyms: 'Zugang, Einladung, beitreten' }, defaults: { name: 'Digitaler Assistent', greeting: 'Hallo!', noAnswer: 'Keine Antwort.' }, state: { activeNow: true, liveChatEnabled: false, kbEnabled: true, helpdeskOffline: false }, departments: [{ id: 1, name: 'Technik' }], priorities: [{ id: 1, name: 'Normal' }] };
|
||||
},
|
||||
'tickets.php': () => ({ items: tickets, page: 1, perPage: 30, total: 4, hasMore: false }),
|
||||
'ticket.php': () => ({ ticket: { ...tickets[0], source: 'web', firstResponseDueAt: null, resolutionDueAt: '2026-10-12 12:00:00', similar: { articles: [{ title: 'Drucker einrichten', url: '/kb_article.php?slug=drucker' }], tickets: [{ number: 'T-77', subject: 'Alter Druckerfall' }] }, lockedByOtherName: null, lockedBy: window.__lockedBy || null, canUnlock: !!window.__lockedBy, customFields: [{ label: 'Titel der Geschichte', type: 'text', value: 'Der kleine Bär' }, { label: 'Nutzerbedingungen', type: 'checkbox', value: 'Ja' }], replySignatures: { default: 'own', own: { available: true, text: 'Viele Grüße\nMax persönlich', html: '<p>Viele Grüße</p><div>Max persönlich</div>', hasImage: false }, department: { available: true, name: 'Technik', text: 'Technik-Team Support', html: '<p>Technik-Team Support</p><img src="https://example.org/logo.png" width="120" onerror="alert(1)" style="width:999px"><a href="javascript:alert(1)">Link</a><scr' + 'ipt>alert(2)</scr' + 'ipt>', hasImage: true } }, threads: [
|
||||
'ticket.php': () => ({ ticket: { ...tickets[0], collaborators: window.__collabs || [{ id: 1, email: 'cc@firma.de', name: 'Cc Person' }], followers: [{ id: 2, name: 'Eva Beispiel' }], csat: { rating: 4, comment: 'Schnell und freundlich', sentAt: '2026-10-12 09:00:00', answeredAt: '2026-10-12 09:30:00' }, source: 'web', firstResponseDueAt: null, resolutionDueAt: '2026-10-12 12:00:00', similar: { articles: [{ title: 'Drucker einrichten', url: '/kb_article.php?slug=drucker' }], tickets: [{ number: 'T-77', subject: 'Alter Druckerfall' }] }, lockedByOtherName: null, lockedBy: window.__lockedBy || null, canUnlock: !!window.__lockedBy, customFields: [{ label: 'Titel der Geschichte', type: 'text', value: 'Der kleine Bär' }, { label: 'Nutzerbedingungen', type: 'checkbox', value: 'Ja' }], replySignatures: { default: 'own', own: { available: true, text: 'Viele Grüße\nMax persönlich', html: '<p>Viele Grüße</p><div>Max persönlich</div>', hasImage: false }, department: { available: true, name: 'Technik', text: 'Technik-Team Support', html: '<p>Technik-Team Support</p><img src="https://example.org/logo.png" width="120" onerror="alert(1)" style="width:999px"><a href="javascript:alert(1)">Link</a><scr' + 'ipt>alert(2)</scr' + 'ipt>', hasImage: true } }, threads: [
|
||||
{ id: 1, authorType: 'user', authorName: 'Kunde 1', message: 'Mein Drucker druckt nichts mehr.\nBitte helft.', isInternalNote: false, createdAt: '2026-10-12 08:00:00', attachments: [{ id: 1, name: 'foto.png', size: 20480 }], comments: [] },
|
||||
{ id: 2, authorType: 'staff', authorName: 'Max Muster', message: 'Wir schauen uns das an.', isInternalNote: false, createdAt: '2026-10-12 08:30:00', attachments: [], comments: [] },
|
||||
{ id: 3, authorType: 'staff', authorName: 'Max Muster', message: 'Treiber prüfen', isInternalNote: true, createdAt: '2026-10-12 08:35:00', attachments: [], comments: [] }] } }),
|
||||
'ticket_collaborators.php': (req) => {
|
||||
window.__collabs = window.__collabs || [{ id: 1, email: 'cc@firma.de', name: 'Cc Person' }];
|
||||
const b = req.body || {};
|
||||
if (b.action === 'add') window.__collabs.push({ id: 10 + window.__collabs.length, email: b.email, name: '' });
|
||||
if (b.action === 'remove') window.__collabs = window.__collabs.filter((c) => c.id !== b.id);
|
||||
return { collaborators: window.__collabs };
|
||||
},
|
||||
'ticket_templates.php': () => ({ templates: [{ id: 1, name: 'Neuer Mitarbeiter', description: '', subject: 'Neuer Mitarbeiter: ', body: 'Name:\nStartdatum:', departmentId: 1, helpTopicId: null, priorityId: 1, isPublic: true }] }),
|
||||
'csat.php': () => ({ enabled: true, days: 30, stats: { sent: 10, answered: 6, avg: 4.2, rate: 60, dist: [1, 2, 3, 4, 5].map((r) => ({ rating: r, count: r === 5 ? 3 : r === 4 ? 2 : r === 1 ? 1 : 0 })), byStaff: [{ id: 1, name: 'Max Muster', count: 4, avg: 4.5 }], recent: [{ rating: 5, comment: 'Top', answeredAt: '2026-10-12 09:30:00', number: 'T-1001', subject: 'Drucker druckt nicht' }] } }),
|
||||
'status.php': (req) => {
|
||||
window.__status = window.__status || { enabled: false, title: 'Systemstatus', url: 'https://demo.example.de/status.php', overall: { key: 'operational', label: 'Alle Systeme laufen', color: '#157a45' },
|
||||
componentStates: [{ key: 'operational', label: 'In Betrieb' }, { key: 'maintenance', label: 'Wartung' }, { key: 'degraded', label: 'Eingeschränkt' }, { key: 'outage', label: 'Ausfall' }],
|
||||
incidentStates: [{ key: 'investigating', label: 'Wird untersucht' }, { key: 'resolved', label: 'Behoben' }],
|
||||
components: [{ id: 1, name: 'Webseite', description: 'Kundenportal', status: 'operational', statusLabel: 'In Betrieb' }], incidents: [] };
|
||||
if (req.method === 'POST') { (window.__statusPosts = window.__statusPosts || []).push(req.body); const b = req.body; if (b.action === 'set_enabled') window.__status.enabled = b.enabled; }
|
||||
return { status: window.__status, canManage: true };
|
||||
},
|
||||
'ticket_unlock.php': () => { window.__lockedBy = null; return { ok: true, unlocked: true }; },
|
||||
'departments.php': () => ({ staffOptions: [{ id: 1, name: 'Max Muster' }, { id: 2, name: 'Eva Beispiel' }], emailOptions: [], slaPlans: [], items: [{ id: 1, name: 'Technik', email: 'technik@example.de', isDefault: true, signature: '', isActive: true, slaPlanId: null, managerId: 1, managerName: 'Max Muster', autoAssignMode: 'staff', autoAssignStaffId: 2, notes: '', members: [{ id: 1, name: 'Max Muster', isPrimary: true }] }] }),
|
||||
'queues.php': () => ({ items: [{ id: 1, name: 'Meine überfälligen', isShared: false, mine: true, ownerName: 'Max Muster', filters: { status: 'open,pending', assigned: 'me', sla: 'overdue' } }, { id: 2, name: 'Team-Überblick', isShared: true, mine: false, ownerName: 'Eva Beispiel', filters: { status: 'open,pending,answered' } }] }),
|
||||
@@ -52,5 +69,5 @@ window.deskhub = {
|
||||
onSessionExpired() {}, onNavigate() {},
|
||||
};
|
||||
</script>
|
||||
<script src="../src/app.js"></script><script src="../src/admin-extra.js"></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>
|
||||
</body></html>
|
||||
|
||||
+46
-3
@@ -47,6 +47,13 @@ app.whenReady().then(async () => {
|
||||
check('Ticketdetail zeigt Eigenschaften und Aktionen', await run(`['eigenschaften', 'schließen'].every(x => document.getElementById('main').innerText.toLowerCase().includes(x))`));
|
||||
check('Ticketdetail: Wiedervorlage- und Beobachten-Knopf sind da', await run(`(() => { const t = document.getElementById('main').innerText; return t.includes('Auf später') && t.includes('Beobachten'); })()`));
|
||||
check('Ticketdetail: „Dazu passend“ zeigt Artikel und ähnliches Ticket', await run(`(() => { const t = document.getElementById('main').innerText; return t.toLowerCase().includes('dazu passend') && t.includes('Drucker einrichten') && t.includes('T-77'); })()`));
|
||||
check('Ticketdetail: Bewertung des Kunden (Sterne und Kommentar)', await run(`(() => { const t = document.getElementById('main').textContent; return t.includes('Kundenzufriedenheit') && t.includes('★★★★☆') && t.includes('4 von 5') && t.includes('Schnell und freundlich'); })()`));
|
||||
check('Ticketdetail: Beobachter und Cc-Empfänger werden gezeigt', await run(`(() => { const t = document.getElementById('main').innerText; return t.includes('Eva Beispiel') && t.includes('Cc Person') && t.includes('cc@firma.de'); })()`));
|
||||
await run(`window.__bodies = {}; (() => { const i = [...document.querySelectorAll('#main input')].find((x) => x.placeholder === 'Cc-Adresse hinzufügen'); i.value = 'neu@kunde.de'; [...document.querySelectorAll('#main button')].find((b) => b.innerText === 'Hinzufügen').click(); })()`);
|
||||
check('Cc hinzufügen sendet die Adresse an den Server und zeigt sie an', await waitFor(`(window.__bodies['ticket_collaborators.php'] || {}).action === 'add' && window.__bodies['ticket_collaborators.php'].email === 'neu@kunde.de' && document.getElementById('main').innerText.includes('neu@kunde.de')`));
|
||||
await run(`window.__bodies = {}; [...document.querySelectorAll('#main .mini.row button')][0].click()`);
|
||||
check('Cc entfernen sendet die Kennung an den Server', await waitFor(`(window.__bodies['ticket_collaborators.php'] || {}).action === 'remove' && window.__bodies['ticket_collaborators.php'].id === 1 && !document.getElementById('main').innerText.includes('Cc Person')`));
|
||||
await run(`window.__collabs = null`);
|
||||
await run(`window.__bodies = {}; [...document.querySelectorAll('#main button')].find((b) => b.innerText.includes('Beobachten') && !b.innerText.includes('Nicht')).click()`);
|
||||
await run(`window.__web = ''; [...document.querySelectorAll('#main button')].find((b) => b.innerText.includes('Im Browser')).click()`);
|
||||
check('Ticket: „Im Browser“ öffnet das Ticket in der Weboberfläche', await waitFor(`String(window.__web).startsWith('/staff/ticket_view.php?n=')`));
|
||||
@@ -76,6 +83,11 @@ app.whenReady().then(async () => {
|
||||
check('Sperre aufheben ruft den Server auf und der Hinweis verschwindet', await waitFor(`window.__calls.includes('ticket_unlock.php') && !document.getElementById('main').innerText.includes('„Florian“ bearbeitet')`));
|
||||
await run(`go('reports')`);
|
||||
check('Berichte laden Tabelle mit Summen', await waitFor(`['Technik', 'Vertrieb', '59'].every(x => document.getElementById('main').innerText.includes(x))`));
|
||||
check('Berichte: Kundenzufriedenheit mit Durchschnitt, Rücklauf und Kommentar', await waitFor(`(() => { const t = document.getElementById('main').textContent; return t.includes('Kundenzufriedenheit') && t.includes('4,2 ★') && t.includes('6 Antworten von 10') && t.includes('Max Muster 4,5') && t.includes('Top'); })()`));
|
||||
await run(`go('newticket')`);
|
||||
check('Neues Ticket: Vorlagenauswahl erscheint', await waitFor(`(() => { const s = [...document.querySelectorAll('#main select')][0]; return !!s && [...s.options].some((o) => o.text === 'Neuer Mitarbeiter'); })()`));
|
||||
await run(`(() => { const s = [...document.querySelectorAll('#main select')][0]; s.value = '1'; s.dispatchEvent(new Event('change')); })()`);
|
||||
check('Vorlage füllt Betreff, Text und Priorität vor', await run(`(() => { const inp = [...document.querySelectorAll('#main input')].map((x) => x.value); const ta = document.querySelector('#main textarea').value; const sel = [...document.querySelectorAll('#main select')].map((x) => x.value); return inp.includes('Neuer Mitarbeiter: ') && ta === 'Name:\\nStartdatum:' && sel.includes('1'); })()`));
|
||||
|
||||
console.log('Verwaltung');
|
||||
await run(`state.staff.role = 'admin'`);
|
||||
@@ -105,6 +117,15 @@ app.whenReady().then(async () => {
|
||||
await tabBtn('Frage ausprobieren');
|
||||
await run(`window.__assistantPosts = []; document.querySelector('#main input').value = 'Wann kommt das Paket?'; [...document.querySelectorAll('#main button')].find((b) => b.innerText === 'Testen').click()`);
|
||||
check('Assistent: Ausprobieren zeigt die Antwort', await waitFor(`document.getElementById('main').innerText.includes('Zwei bis drei Werktage.')`));
|
||||
// Statusseite: Zustand der Dienste, Einschalten, Meldung veröffentlichen
|
||||
await run(`go('admin', 'statuspage')`);
|
||||
check('Statusseite: Dienste und Gesamtzustand werden gezeigt', await waitFor(`(() => { const t = document.getElementById('main').textContent; return t.includes('Alle Systeme laufen') && t.includes('Webseite') && t.includes('Neue Meldung'); })()`));
|
||||
await run(`window.__statusPosts = []; document.querySelector('#main input[type=checkbox]').click()`);
|
||||
check('Statusseite: Einschalten sendet set_enabled', await waitFor(`(window.__statusPosts[0] || {}).action === 'set_enabled' && window.__statusPosts[0].enabled === true`));
|
||||
await run(`window.__statusPosts = []; (() => { const t = [...document.querySelectorAll('#main input')].find((x) => x.placeholder.includes('E-Mail-Versand')); t.value = 'Portal langsam'; [...document.querySelectorAll('#main button')].find((b) => b.innerText === 'Veröffentlichen').click(); })()`);
|
||||
check('Statusseite: Meldung veröffentlichen sendet Titel und Stufe', await waitFor(`(window.__statusPosts[0] || {}).action === 'create_incident' && window.__statusPosts[0].title === 'Portal langsam' && window.__statusPosts[0].status === 'investigating'`));
|
||||
await run(`window.__statusPosts = []; (() => { const s = [...document.querySelectorAll('#main select')][0]; s.value = 'outage'; s.dispatchEvent(new Event('change')); })()`);
|
||||
check('Statusseite: Zustand eines Dienstes ändern sendet set_component', await waitFor(`(window.__statusPosts[0] || {}).action === 'set_component' && window.__statusPosts[0].status === 'outage' && window.__statusPosts[0].id === 1`));
|
||||
// Rechte: ohne das Recht ist der Bereich nicht in der Verwaltung
|
||||
await run(`state.staff.role = 'agent'; state.staff.permissions = {}`);
|
||||
check('Assistent: ohne Recht nicht in den Verwaltungsbereichen', await run(`!adminSections().some((s) => s.key === 'assistant')`));
|
||||
@@ -114,9 +135,8 @@ app.whenReady().then(async () => {
|
||||
|
||||
// Abteilung: automatische Zuweisung neuer Tickets einstellen
|
||||
await run(`go('admin')`);
|
||||
check('Verwaltung zeigt die Weboberfläche-Seiten, die der Server freigibt', await waitFor(`(() => { const t = document.getElementById('main').innerText; return t.includes('Backup') && t.includes('Einstellungen') && t.includes('im Browser'); })()`));
|
||||
await run(`[...document.querySelectorAll('#main .tile')].find((x) => x.innerText.includes('Backup')).click()`);
|
||||
check('Klick öffnet die Seite über die einmalige Anmeldung (nur /staff/-Pfad)', await waitFor(`window.__web === '/staff/backup.php'`));
|
||||
check('Verwaltung zeigt nur Bereiche der App, keine Seiten der Weboberfläche mehr', await waitFor(`(() => { const t = document.getElementById('main').innerText; return t.includes('Abteilungen') && !t.includes('(im Browser)') && !t.includes('Backup') && !document.querySelector('#main .tile[data-web]'); })()`));
|
||||
check('Verwaltung ruft die Seiten der Weboberfläche nicht mehr ab', await run(`!window.__calls.includes('web_pages.php')`));
|
||||
await run(`go('admin', 'departments')`);
|
||||
check('Abteilungen: Liste lädt', await waitFor(`document.getElementById('main').innerText.includes('Technik')`));
|
||||
await run(`document.querySelector('#main tr.click').click()`);
|
||||
@@ -143,6 +163,29 @@ app.whenReady().then(async () => {
|
||||
await run(`window.__health = [{ level: 'error', code: 'mailbox_down', message: 'Postfach „support@example.de“ ist nicht erreichbar.' }]; pollActivity()`);
|
||||
check('Serverstörung (Mailabruf) wird als Banner gezeigt', await waitFor(`document.getElementById('banners').innerText.includes('support@example.de')`, 6000));
|
||||
|
||||
console.log('Sprache');
|
||||
check('Standard (Windows-Sprache im Test: Deutsch): Flaggen sind in der Kopfleiste', await run(`document.querySelectorAll('.topbar .lang-flag').length === 2 && document.querySelector('.topbar .lang-flag.on').title === 'Deutsch'`));
|
||||
check('Deutsch: Seitenleiste deutsch', await run(`document.querySelector('.side').innerText.includes('Übersicht')`));
|
||||
await run(`localStorage.setItem('lang', 'en')`);
|
||||
await win.loadFile(path.join(__dirname, 'mock.html'));
|
||||
check('English: App startet', await waitFor(`!!document.querySelector('.side') && !!document.getElementById('main')`));
|
||||
check('English: Seitenleiste und Kopfleiste übersetzt', await run(`(() => { const t = document.querySelector('.side').innerText + document.querySelector('.topbar').innerText; return t.includes('Overview') && t.includes('Tasks') && t.includes('Sign out') && !t.includes('Abmelden'); })()`));
|
||||
check('English: Flagge English ist aktiv, <html lang> passt', await run(`document.querySelector('.topbar .lang-flag.on').title === 'English' && document.documentElement.lang === 'en'`));
|
||||
await run(`go('ticket', 1)`);
|
||||
check('English: Ticket übersetzt (Eigenschaften, Aktionen, Texte mit Zahlen)', await waitFor(`(() => { const t = document.getElementById('main').textContent; return t.includes('Properties') && t.includes('Resolve') && t.includes('Customer satisfaction') && t.includes('Mein Drucker druckt nichts mehr') && t.includes('Drucker druckt nicht'); })()`));
|
||||
check('English: Kundendaten (Ticketinhalt) bleiben unübersetzt', await run(`document.getElementById('main').textContent.includes('Mein Drucker druckt nichts mehr')`));
|
||||
await run(`go('newticket')`);
|
||||
check('English: Neues Ticket (Beschriftungen und Platzhalter)', await waitFor(`(() => { const t = document.getElementById('main').textContent; return t.includes('New ticket') && t.includes('Customer name') && t.includes('Subject') && t.includes('Create ticket'); })()`));
|
||||
await run(`go('tickets')`);
|
||||
check('English: Ticketliste hat Reiter und Suche auf English', await waitFor(`(() => { const t = document.getElementById('main').textContent; return t.includes('Watched') || t.includes('Snoozed') || t.includes('Open'); })()`));
|
||||
check('English: Datumsformat englisch', await run(`fmtDate('2026-10-24 19:42:00').includes('24/10/2026')`));
|
||||
await run(`state.staff.role = 'admin'; go('admin')`);
|
||||
check('English: Verwaltung (Kacheln, Hinweis ohne Web-Verweis)', await waitFor(`(() => { const t = document.getElementById('main').textContent; return t.includes('Administration') && t.includes('Departments') && t.includes('Only areas you are authorized for.'); })()`));
|
||||
await run(`[...document.querySelectorAll('.topbar .lang-flag')].find((b) => b.title === 'Deutsch').click()`);
|
||||
await waitFor(`localStorage.getItem('lang') === 'de'`);
|
||||
check('Klick auf die deutsche Flagge speichert die Sprache', await run(`localStorage.getItem('lang') === 'de'`));
|
||||
await run(`localStorage.removeItem('lang')`);
|
||||
|
||||
check('Keine JavaScript-Fehler in der Konsole', consoleErrors.length === 0, consoleErrors.slice(0, 3).join(' | '));
|
||||
} catch (e) {
|
||||
failures.push('Testlauf abgebrochen: ' + e.message);
|
||||
|
||||
Reference in New Issue
Block a user