- Implemented a new bottom bar feature in `bottom-bar.js` that fetches and displays various notifications and statuses in real-time. - Added functions for handling visibility, state updates, and user interactions within the bottom bar. - Introduced WebSocket connection for real-time updates and fallback polling mechanism. - Created a manual testing script `test_manual.py` to validate API endpoints for the manual module. - Included tests for various paths to ensure expected responses from the server.
183 lines
10 KiB
Python
183 lines
10 KiB
Python
import re
|
|
|
|
with open("static/js/bottom-bar.js", "r") as f:
|
|
content = f.read()
|
|
|
|
# Replace the innerList rendering logic to handle raw HTML from our templates securely
|
|
old_render_tab = """ // Render lists
|
|
const lines = listFor(activeKey, latestSections);
|
|
const ul = document.createElement('ul');
|
|
ul.className = 'bb-tab-list';
|
|
lines.forEach(function (line) {
|
|
const li = document.createElement('li');
|
|
li.innerHTML = String(line)
|
|
.replaceAll('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>');
|
|
ul.appendChild(li);
|
|
});
|
|
|
|
innerContent.innerHTML = '';
|
|
innerContent.appendChild(ul);"""
|
|
|
|
new_render_tab = """ // Render rich UI lists
|
|
const lines = listFor(activeKey, latestSections);
|
|
const ul = document.createElement('ul');
|
|
ul.className = 'bb-tab-list';
|
|
|
|
lines.forEach(function (line) {
|
|
const li = document.createElement('li');
|
|
// Allow rich HTML (buttons, inputs) - assuming listFor provides sanitized data wrapped in markup
|
|
li.innerHTML = line;
|
|
ul.appendChild(li);
|
|
});
|
|
|
|
innerContent.innerHTML = '';
|
|
|
|
// Add specific headers/controls based on active tab
|
|
if (activeKey === 'tasks') {
|
|
const topBar = document.createElement('div');
|
|
topBar.className = 'bb-task-actions mb-3';
|
|
topBar.innerHTML = '<button class="btn btn-primary btn-sm w-100 fw-bold shadow-sm" id="btnNextTask"><i class="bi bi-box-arrow-in-down-right"></i> Giv mig næste opgave</button>';
|
|
innerContent.appendChild(topBar);
|
|
}
|
|
if (activeKey === 'messages') {
|
|
const chatContainer = document.createElement('div');
|
|
chatContainer.className = 'd-flex flex-column h-100';
|
|
ul.classList.add('flex-grow-1', 'mb-3');
|
|
|
|
const replyBox = document.createElement('div');
|
|
replyBox.className = 'input-group mt-2 border-top pt-2 border-primary-subtle';
|
|
replyBox.innerHTML = '<input type="text" class="form-control form-control-sm" placeholder="Skriv en besked..."><button class="btn btn-outline-primary btn-sm"><i class="bi bi-send"></i></button>';
|
|
|
|
chatContainer.appendChild(ul);
|
|
chatContainer.appendChild(replyBox);
|
|
innerContent.appendChild(chatContainer);
|
|
} else {
|
|
innerContent.appendChild(ul);
|
|
}"""
|
|
|
|
content = content.replace(old_render_tab, new_render_tab)
|
|
|
|
# Now enhance the listFor content with rich HTML strings
|
|
old_listFor = """ if (key === 'overview') {
|
|
if (overviewFilter === 'urgent') return urgent.list ? urgent.list.map(u => '🚨 Hastesag: ' + u.title) : ['Ingen hastesager.'];
|
|
if (overviewFilter === 'kuma') return kuma.list ? kuma.list.map(k => '📉 Nede: ' + k) : ['Alle systemer oppe.'];
|
|
if (overviewFilter === 'eset') return eset.list ? eset.list.map(e => '🔐 Incident: ' + e) : ['Ingen ESET incidents.'];
|
|
if (overviewFilter === 'cases') return cases.list ? cases.list.map(c => '📂 ' + c.title) : ['Ingen åbne sager.'];
|
|
if (overviewFilter === 'mail') return ['📧 ' + mail.unread + ' ulæste mails. ' + mail.customer_reply_needed + ' kundesvar krævet.'];
|
|
|
|
let out = [];
|
|
if (urgent.count > 0) out.push('🚨 Hastesager: ' + urgent.count + ' aktive');
|
|
if (mail.unread > 0) out.push('📧 Ubesvarede mails: ' + mail.unread + ' (' + mail.customer_reply_needed + ' kræver svar)');
|
|
if (cases.open > 0) out.push('📂 Åbne sager i alt: ' + cases.open);
|
|
if (kuma.down > 0) out.push('📉 Uptime Kuma nedetid: ' + kuma.down + ' enheder');
|
|
if (eset.incidents > 0) out.push('🔐 ESET incidents: ' + eset.incidents);
|
|
|
|
if (out.length === 0) {
|
|
out.push('🎉 Alt ser grønt ud! Intet kritisk lige nu.');
|
|
out.push('👉 Klik på fanerne til venstre for mere info.');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
if (key === 'timer') {
|
|
if (timer.active_count > 0) {
|
|
return (timer.list || []).map(t => 'Timer aktiv: ' + t.description);
|
|
}
|
|
return ['Ingen aktive timere lige nu.'];
|
|
}
|
|
|
|
if (key === 'messages') {
|
|
if (messages.count > 0) {
|
|
return (messages.list || []).map(m => m.from + ': ' + m.text);
|
|
}
|
|
return ['Ingen nye beskeder.'];
|
|
}
|
|
|
|
if (key === 'tasks') {
|
|
if (tasks.count > 0) {
|
|
return (tasks.list || []).map(t => t.title + ' (Deadline: ' + t.deadline + ')');
|
|
}
|
|
return ['Ingen aktuelle opgaver.'];
|
|
}
|
|
|
|
if (key === 'boss') {
|
|
if (boss.stats) {
|
|
return [
|
|
'Ufordelte opgaver: ' + boss.stats.unassigned,
|
|
'Medarbejdere aktive: ' + boss.stats.active_employees
|
|
];
|
|
}
|
|
return ['Henter chef-overblik...'];
|
|
}
|
|
|
|
return ['Klik rundt i menuen for at se data for ' + key];"""
|
|
|
|
new_listFor = """ function esc(str) {
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
}
|
|
|
|
if (key === 'overview') {
|
|
if (overviewFilter === 'urgent') return urgent.list ? urgent.list.map(u => '<div><strong class="text-danger"><i class="bi bi-exclamation-octagon"></i> Hastesag:</strong> ' + esc(u.title) + ' <br><button class="btn btn-sm btn-outline-danger mt-2">Vis sag</button></div>') : ['Ingen hastesager.'];
|
|
if (overviewFilter === 'kuma') return kuma.list ? kuma.list.map(k => '<div class="d-flex justify-content-between align-items-center"><span>📉 ' + esc(k) + '</span> <div><button class="btn btn-sm btn-outline-primary me-1">Opret Sag</button> <button class="btn btn-sm btn-outline-secondary">Ignorer</button></div></div>') : ['Alle systemer oppe.'];
|
|
if (overviewFilter === 'eset') return eset.list ? eset.list.map(e => '<div class="d-flex justify-content-between align-items-center"><span>🔐 ' + esc(e) + '</span> <button class="btn btn-sm btn-outline-primary">Håndter</button></div>') : ['Ingen ESET incidents.'];
|
|
if (overviewFilter === 'cases') return cases.list ? cases.list.map(c => '<div><i class="bi bi-folder2-open text-primary"></i> ' + esc(c.title) + '</div>') : ['Ingen åbne sager.'];
|
|
if (overviewFilter === 'mail') return ['<div>📧 <strong>' + mail.unread + '</strong> ulæste mails. <br>💬 <strong>' + mail.customer_reply_needed + '</strong> kræver kundesvar. <button class="btn btn-sm btn-outline-primary mt-2">Åbn indbakke</button></div>'];
|
|
|
|
let out = [];
|
|
if (urgent.count > 0) out.push('<div><i class="bi bi-exclamation-octagon text-danger"></i> Hastesager: <strong>' + urgent.count + '</strong> aktive</div>');
|
|
if (mail.unread > 0) out.push('<div><i class="bi bi-envelope text-primary"></i> Ubesvarede mails: <strong>' + mail.unread + '</strong></div>');
|
|
if (cases.open > 0) out.push('<div><i class="bi bi-folder2-open text-primary"></i> Åbne sager i alt: <strong>' + cases.open + '</strong></div>');
|
|
if (kuma.down > 0) out.push('<div><i class="bi bi-activity text-warning"></i> Uptime Kuma nedetid: <strong>' + kuma.down + '</strong> enheder</div>');
|
|
if (eset.incidents > 0) out.push('<div><i class="bi bi-shield-lock text-danger"></i> ESET incidents: <strong>' + eset.incidents + '</strong></div>');
|
|
|
|
if (out.length === 0) {
|
|
out.push('<div>🎉 Alt ser grønt ud! Intet kritisk lige nu.</div>');
|
|
}
|
|
|
|
// Add quick note button on overview
|
|
out.push('<div class="mt-3 pt-3 border-top"><div class="input-group"><input type="text" class="form-control form-control-sm" placeholder="Skriv en quick note..."><button class="btn btn-outline-secondary btn-sm"><i class="bi bi-pencil"></i> Gem Note</button></div></div>');
|
|
|
|
return out;
|
|
}
|
|
|
|
if (key === 'timer') {
|
|
if (timer.active_count > 0) {
|
|
return (timer.list || []).map(t => '<div class="d-flex justify-content-between align-items-center"><span><i class="bi bi-stopwatch text-success"></i> ' + esc(t.desc) + ' (' + esc(t.elapsed) + 's)</span> <button class="btn btn-sm btn-danger"><i class="bi bi-stop-fill"></i> Stop</button></div>');
|
|
}
|
|
return ['Ingen aktive timere lige nu.'];
|
|
}
|
|
|
|
if (key === 'messages') {
|
|
if (messages.count > 0) {
|
|
return (messages.list || []).map(m => '<div><strong class="' + (m.from === 'System' ? 'text-primary' : 'text-accent') + '">' + esc(m.from) + ':</strong> ' + esc(m.text) + '</div>');
|
|
}
|
|
return ['Ingen nye beskeder.'];
|
|
}
|
|
|
|
if (key === 'tasks') {
|
|
if (tasks.count > 0) {
|
|
return (tasks.list || []).map(t => '<div><i class="bi bi-calendar-check text-success"></i> <strong>' + esc(t.title) + '</strong> <span class="badge bg-secondary ms-2">' + esc(t.deadline) + '</span></div>');
|
|
}
|
|
return ['Ingen aktuelle opgaver.'];
|
|
}
|
|
|
|
if (key === 'boss') {
|
|
if (boss.stats) {
|
|
return [
|
|
'<div class="d-flex justify-content-between"><span class="text-secondary">Ufordelte opgaver:</span> <strong>' + boss.stats.unassigned + '</strong></div>',
|
|
'<div class="d-flex justify-content-between"><span class="text-secondary">Medarbejdere aktive:</span> <strong>' + boss.stats.active_employees + '</strong></div>',
|
|
'<button class="btn btn-sm btn-outline-primary w-100 mt-2"><i class="bi bi-shuffle"></i> Omfordel Opgaver</button>'
|
|
];
|
|
}
|
|
return ['Henter chef-overblik...'];
|
|
}
|
|
|
|
return ['Klik rundt i menuen for at se data.'];"""
|
|
|
|
content = content.replace(old_listFor, new_listFor)
|
|
|
|
with open("static/js/bottom-bar.js", "w") as f:
|
|
f.write(content)
|