UI: Move clock/date to right side, remove refresh button, and improve responsive layout
This commit is contained in:
parent
183b9528fe
commit
c14cd60a32
1 changed files with 60 additions and 392 deletions
|
|
@ -114,25 +114,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Center Clock & Date - Moved to z-0 and using better responsive classes -->
|
|
||||||
<div class="absolute left-1/2 transform -translate-x-1/2 hidden lg:flex flex-col items-center -z-10 opacity-50 pointer-events-none">
|
|
||||||
<div class="text-xl font-bold text-white font-mono tracking-wider leading-none" id="live-clock">--:--:--</div>
|
|
||||||
<div class="text-[10px] text-gray-400 font-medium uppercase tracking-widest mt-1" id="live-date">--. --. ----</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav class="hidden md:flex items-center space-x-8">
|
<nav class="hidden md:flex items-center space-x-8">
|
||||||
<a href="#weather-section" class="text-sm font-medium text-gray-400 hover:text-white transition-colors">Wetter</a>
|
<a href="#weather-section" class="text-sm font-medium text-gray-400 hover:text-white transition-colors">Wetter</a>
|
||||||
<a href="#news-section" class="text-sm font-medium text-gray-400 hover:text-white transition-colors">News</a>
|
<a href="#news-section" class="text-sm font-medium text-gray-400 hover:text-white transition-colors">News</a>
|
||||||
<a href="#tasks-section" class="text-sm font-medium text-gray-400 hover:text-white transition-colors">Tasks</a>
|
<a href="#tasks-section" class="text-sm font-medium text-gray-400 hover:text-white transition-colors">Tasks</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="flex items-center space-x-3">
|
<!-- Clock & Date moved to the right (replacing refresh button) -->
|
||||||
<button onclick="manualRefresh()" id="refresh-btn" class="px-3 py-1.5 bg-gray-800 hover:bg-gray-700 rounded-lg text-sm transition-colors flex items-center space-x-2">
|
<div class="flex flex-col items-end min-w-[100px]">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<div class="text-xl font-bold text-white font-mono tracking-wider leading-none" id="live-clock">--:--:--</div>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
<div class="text-[10px] text-gray-400 font-medium uppercase tracking-widest mt-1" id="live-date">--. --. ----</div>
|
||||||
</svg>
|
|
||||||
<span>Refresh</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -274,8 +265,6 @@
|
||||||
{% if system_status.cached %}<span class="cached-badge">cached</span>{% endif %}
|
{% if system_status.cached %}<span class="cached-badge">cached</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Services Status - REMOVED AS REQUESTED -->
|
|
||||||
|
|
||||||
<!-- CPU & RAM -->
|
<!-- CPU & RAM -->
|
||||||
<div class="border-t border-gray-700 pt-4 space-y-3">
|
<div class="border-t border-gray-700 pt-4 space-y-3">
|
||||||
<!-- CPU -->
|
<!-- CPU -->
|
||||||
|
|
@ -336,17 +325,6 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% if ha_status.covers %}
|
|
||||||
<div class="pt-2 border-t border-gray-700">
|
|
||||||
<div class="text-xs text-gray-500 mb-2">Rolläden</div>
|
|
||||||
{% for cover in ha_status.covers %}
|
|
||||||
<div class="flex items-center justify-between text-sm">
|
|
||||||
<span class="text-gray-300 truncate">{{ cover.name }}</span>
|
|
||||||
<span class="text-gray-400">{{ cover.state }}</span>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="flex items-center space-x-2 text-red-400">
|
<div class="flex items-center space-x-2 text-red-400">
|
||||||
|
|
@ -358,7 +336,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Row 3: Private Tasks (Haus & Garten + Jugendeinrichtung) -->
|
<!-- Row 3: Tasks Sections -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<!-- Private Tasks -->
|
||||||
<div class="glass-card rounded-xl p-5 fade-in" id="tasks-private-card">
|
<div class="glass-card rounded-xl p-5 fade-in" id="tasks-private-card">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-base font-semibold text-gray-200 flex items-center">
|
<h2 class="text-base font-semibold text-gray-200 flex items-center">
|
||||||
|
|
@ -368,71 +348,30 @@
|
||||||
Private Aufgaben
|
Private Aufgaben
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<span class="text-blue-400 font-bold">{{ vikunja_all.private.open_count }}</span>
|
<span class="text-blue-400 font-bold">{{ vikunja_all.private.open_count }}</span>
|
||||||
<span class="text-gray-400 text-sm">offen</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<span class="text-gray-500 font-bold">{{ vikunja_all.private.done_count }}</span>
|
|
||||||
<span class="text-gray-500 text-sm">erledigt</span>
|
|
||||||
</div>
|
|
||||||
{% if vikunja_all.cached %}<span class="cached-badge">cached</span>{% endif %}
|
{% if vikunja_all.cached %}<span class="cached-badge">cached</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabs -->
|
|
||||||
<div class="flex space-x-6 border-b border-gray-700 mb-4">
|
|
||||||
<button onclick="switchTabPrivate('open')" id="tab-private-open" class="pb-2 text-sm font-medium tab-active transition-colors">
|
|
||||||
Offen ({{ vikunja_all.private.open_count }})
|
|
||||||
</button>
|
|
||||||
<button onclick="switchTabPrivate('done')" id="tab-private-done" class="pb-2 text-sm font-medium text-gray-400 hover:text-gray-200 transition-colors">
|
|
||||||
Erledigt ({{ vikunja_all.private.done_count }})
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Open Tasks -->
|
|
||||||
<div id="tasks-private-open" class="space-y-2 max-h-64 overflow-y-auto">
|
<div id="tasks-private-open" class="space-y-2 max-h-64 overflow-y-auto">
|
||||||
{% if vikunja_all.private.open %}
|
{% if vikunja_all.private.open %}
|
||||||
{% for task in vikunja_all.private.open %}
|
{% for task in vikunja_all.private.open %}
|
||||||
<div class="flex items-start space-x-3 p-3 bg-gray-800/50 rounded-lg hover:bg-gray-800 transition-colors">
|
<div class="flex items-start space-x-3 p-3 bg-gray-800/50 rounded-lg">
|
||||||
<span class="text-blue-400 mt-0.5">□</span>
|
<span class="text-blue-400 mt-0.5">□</span>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<a href="http://10.10.10.10:3456/tasks/{{ task.id }}" target="_blank" class="text-gray-200 hover:text-blue-400 transition-colors cursor-pointer">{{ task.title }}</a>
|
<a href="http://10.10.10.10:3456/tasks/{{ task.id }}" target="_blank" class="text-gray-200 hover:text-blue-400 transition-colors cursor-pointer">{{ task.title }}</a>
|
||||||
<div class="flex items-center space-x-2 text-xs text-gray-500 mt-1">
|
<div class="flex items-center space-x-2 text-xs text-gray-500 mt-1">
|
||||||
<span class="px-2 py-0.5 bg-gray-700 rounded">{{ task.project }}</span>
|
<span class="px-2 py-0.5 bg-gray-700 rounded">{{ task.project }}</span>
|
||||||
{% if task.priority > 0 %}
|
|
||||||
<span class="text-yellow-400">★ {{ task.priority }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-gray-500 text-center py-8">Keine offenen Aufgaben 🎉</div>
|
<div class="text-gray-500 text-center py-8 text-sm">Keine offenen Aufgaben</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Done Tasks -->
|
|
||||||
<div id="tasks-private-done" class="space-y-2 max-h-64 overflow-y-auto hidden">
|
|
||||||
{% if vikunja_all.private.done %}
|
|
||||||
{% for task in vikunja_all.private.done %}
|
|
||||||
<div class="flex items-start space-x-3 p-3 bg-gray-800/30 rounded-lg">
|
|
||||||
<span class="text-green-500 mt-0.5">✓</span>
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<a href="http://10.10.10.10:3456/tasks/{{ task.id }}" target="_blank" class="text-gray-500 task-done hover:text-gray-400 transition-colors cursor-pointer">{{ task.title }}</a>
|
|
||||||
<div class="text-xs text-gray-600 mt-1">
|
|
||||||
<span class="px-2 py-0.5 bg-gray-800 rounded">{{ task.project }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<div class="text-gray-500 text-center py-8">Noch keine erledigten Aufgaben</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Row 4: Sam's Tasks (OpenClaw AI Tasks + Sam's Wunderwelt) -->
|
<!-- Sam's Tasks -->
|
||||||
<div class="glass-card rounded-xl p-5 fade-in" id="tasks-sam-card">
|
<div class="glass-card rounded-xl p-5 fade-in" id="tasks-sam-card">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-base font-semibold text-gray-200 flex items-center">
|
<h2 class="text-base font-semibold text-gray-200 flex items-center">
|
||||||
|
|
@ -442,74 +381,33 @@
|
||||||
Sam's Aufgaben
|
Sam's Aufgaben
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<span class="text-pink-400 font-bold">{{ vikunja_all.sam.open_count }}</span>
|
<span class="text-pink-400 font-bold">{{ vikunja_all.sam.open_count }}</span>
|
||||||
<span class="text-gray-400 text-sm">offen</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<span class="text-gray-500 font-bold">{{ vikunja_all.sam.done_count }}</span>
|
|
||||||
<span class="text-gray-500 text-sm">erledigt</span>
|
|
||||||
</div>
|
|
||||||
{% if vikunja_all.cached %}<span class="cached-badge">cached</span>{% endif %}
|
{% if vikunja_all.cached %}<span class="cached-badge">cached</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabs -->
|
|
||||||
<div class="flex space-x-6 border-b border-gray-700 mb-4">
|
|
||||||
<button onclick="switchTabSam('open')" id="tab-sam-open" class="pb-2 text-sm font-medium tab-active transition-colors">
|
|
||||||
Offen ({{ vikunja_all.sam.open_count }})
|
|
||||||
</button>
|
|
||||||
<button onclick="switchTabSam('done')" id="tab-sam-done" class="pb-2 text-sm font-medium text-gray-400 hover:text-gray-200 transition-colors">
|
|
||||||
Erledigt ({{ vikunja_all.sam.done_count }})
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Open Tasks -->
|
|
||||||
<div id="tasks-sam-open" class="space-y-2 max-h-64 overflow-y-auto">
|
<div id="tasks-sam-open" class="space-y-2 max-h-64 overflow-y-auto">
|
||||||
{% if vikunja_all.sam.open %}
|
{% if vikunja_all.sam.open %}
|
||||||
{% for task in vikunja_all.sam.open %}
|
{% for task in vikunja_all.sam.open %}
|
||||||
<div class="flex items-start space-x-3 p-3 bg-gray-800/50 rounded-lg hover:bg-gray-800 transition-colors">
|
<div class="flex items-start space-x-3 p-3 bg-gray-800/50 rounded-lg">
|
||||||
<span class="text-pink-400 mt-0.5">□</span>
|
<span class="text-pink-400 mt-0.5">□</span>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<a href="http://10.10.10.10:3456/tasks/{{ task.id }}" target="_blank" class="text-gray-200 hover:text-pink-400 transition-colors cursor-pointer">{{ task.title }}</a>
|
<a href="http://10.10.10.10:3456/tasks/{{ task.id }}" target="_blank" class="text-gray-200 hover:text-pink-400 transition-colors cursor-pointer">{{ task.title }}</a>
|
||||||
<div class="flex items-center space-x-2 text-xs text-gray-500 mt-1">
|
<div class="flex items-center space-x-2 text-xs text-gray-500 mt-1">
|
||||||
<span class="px-2 py-0.5 bg-gray-700 rounded">{{ task.project }}</span>
|
<span class="px-2 py-0.5 bg-gray-700 rounded">{{ task.project }}</span>
|
||||||
{% if task.priority > 0 %}
|
|
||||||
<span class="text-yellow-400">★ {{ task.priority }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-gray-500 text-center py-8">Keine offenen Aufgaben 🎉</div>
|
<div class="text-gray-500 text-center py-8 text-sm">Keine offenen Aufgaben</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Done Tasks -->
|
|
||||||
<div id="tasks-sam-done" class="space-y-2 max-h-64 overflow-y-auto hidden">
|
|
||||||
{% if vikunja_all.sam.done %}
|
|
||||||
{% for task in vikunja_all.sam.done %}
|
|
||||||
<div class="flex items-start space-x-3 p-3 bg-gray-800/30 rounded-lg">
|
|
||||||
<span class="text-green-500 mt-0.5">✓</span>
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<a href="http://10.10.10.10:3456/tasks/{{ task.id }}" target="_blank" class="text-gray-500 task-done hover:text-gray-400 transition-colors cursor-pointer">{{ task.title }}</a>
|
|
||||||
<div class="text-xs text-gray-600 mt-1">
|
|
||||||
<span class="px-2 py-0.5 bg-gray-800 rounded">{{ task.project }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<div class="text-gray-500 text-center py-8">Noch keine erledigten Aufgaben</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let ws = null;
|
let ws = null;
|
||||||
let currentTab = 'open';
|
|
||||||
|
|
||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
|
|
@ -538,64 +436,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDashboard(data) {
|
function updateDashboard(data) {
|
||||||
// Update Weather
|
|
||||||
if (data.weather && !data.weather.error) {
|
if (data.weather && !data.weather.error) {
|
||||||
document.getElementById('weather-temp').textContent = data.weather.temp + '°';
|
document.getElementById('weather-temp').textContent = data.weather.temp + '°';
|
||||||
document.getElementById('weather-icon').textContent = data.weather.icon;
|
document.getElementById('weather-icon').textContent = data.weather.icon;
|
||||||
document.getElementById('weather-desc').textContent = data.weather.description;
|
document.getElementById('weather-desc').textContent = data.weather.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Secondary Weather
|
|
||||||
if (data.weather_secondary && !data.weather_secondary.error) {
|
|
||||||
document.getElementById('weather-secondary-temp').textContent = data.weather_secondary.temp + '°';
|
|
||||||
document.getElementById('weather-secondary-icon').textContent = data.weather_secondary.icon;
|
|
||||||
document.getElementById('weather-secondary-desc').textContent = data.weather_secondary.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Hourly Weather
|
|
||||||
if (data.hourly_weather && data.hourly_weather.Leverkusen) {
|
|
||||||
const container = document.getElementById('hourly-container');
|
|
||||||
container.innerHTML = '';
|
|
||||||
data.hourly_weather.Leverkusen.slice(0, 8).forEach(hour => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'flex-shrink-0 text-center p-2 bg-gray-800/40 rounded-lg min-w-[70px]';
|
|
||||||
div.innerHTML = `
|
|
||||||
<div class="text-[10px] text-gray-500 mb-1">${hour.time}</div>
|
|
||||||
<div class="text-xl mb-1">${hour.icon}</div>
|
|
||||||
<div class="text-sm font-bold">${hour.temp}°</div>
|
|
||||||
<div class="text-[9px] text-gray-400">${hour.precip}%</div>
|
|
||||||
`;
|
|
||||||
container.appendChild(div);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update News
|
|
||||||
if (data.news && data.news.length > 0) {
|
|
||||||
const container = document.getElementById('news-container');
|
|
||||||
container.innerHTML = '';
|
|
||||||
data.news.slice(0, 12).forEach(item => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'glass-card rounded-xl p-4 flex flex-col h-full hover:scale-[1.02] transition-transform';
|
|
||||||
div.innerHTML = `
|
|
||||||
<div class="flex items-center justify-between mb-2">
|
|
||||||
<span class="text-[10px] font-bold uppercase tracking-wider text-gray-500">${item.source}</span>
|
|
||||||
<span class="text-[9px] text-gray-600">${item.time}</span>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-sm font-medium text-gray-200 line-clamp-3 mb-3 flex-grow">
|
|
||||||
${item.title}
|
|
||||||
</h3>
|
|
||||||
<a href="${item.url}" target="_blank" class="text-blue-400 text-xs font-semibold flex items-center hover:text-blue-300">
|
|
||||||
Mehr lesen
|
|
||||||
<svg class="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
`;
|
|
||||||
container.appendChild(div);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update System Status
|
|
||||||
if (data.system_status) {
|
if (data.system_status) {
|
||||||
document.getElementById('cpu-percent').textContent = data.system_status.cpu.percent + '%';
|
document.getElementById('cpu-percent').textContent = data.system_status.cpu.percent + '%';
|
||||||
document.getElementById('cpu-bar').style.width = data.system_status.cpu.percent + '%';
|
document.getElementById('cpu-bar').style.width = data.system_status.cpu.percent + '%';
|
||||||
|
|
@ -603,182 +448,11 @@
|
||||||
`${data.system_status.ram.used_gb}/${data.system_status.ram.total_gb} GB (${data.system_status.ram.percent}%)`;
|
`${data.system_status.ram.used_gb}/${data.system_status.ram.total_gb} GB (${data.system_status.ram.percent}%)`;
|
||||||
document.getElementById('ram-bar').style.width = data.system_status.ram.percent + '%';
|
document.getElementById('ram-bar').style.width = data.system_status.ram.percent + '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update HA
|
|
||||||
if (data.ha_status && data.ha_status.online) {
|
|
||||||
// Could update HA content here
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Tasks
|
|
||||||
if (data.vikunja_all) {
|
|
||||||
// Update task counts
|
|
||||||
// Could refresh task lists here if needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchTabPrivate(tab) {
|
|
||||||
document.getElementById('tasks-private-open').classList.toggle('hidden', tab !== 'open');
|
|
||||||
document.getElementById('tasks-private-done').classList.toggle('hidden', tab !== 'done');
|
|
||||||
document.getElementById('tab-private-open').classList.toggle('tab-active', tab === 'open');
|
|
||||||
document.getElementById('tab-private-open').classList.toggle('text-gray-400', tab !== 'open');
|
|
||||||
document.getElementById('tab-private-done').classList.toggle('tab-active', tab === 'done');
|
|
||||||
document.getElementById('tab-private-done').classList.toggle('text-gray-400', tab !== 'done');
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchTabSam(tab) {
|
|
||||||
document.getElementById('tasks-sam-open').classList.toggle('hidden', tab !== 'open');
|
|
||||||
document.getElementById('tasks-sam-done').classList.toggle('hidden', tab !== 'done');
|
|
||||||
document.getElementById('tab-sam-open').classList.toggle('tab-active', tab === 'open');
|
|
||||||
document.getElementById('tab-sam-open').classList.toggle('text-gray-400', tab !== 'open');
|
|
||||||
document.getElementById('tab-sam-done').classList.toggle('tab-active', tab === 'done');
|
|
||||||
document.getElementById('tab-sam-done').classList.toggle('text-gray-400', tab !== 'done');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function manualRefresh() {
|
|
||||||
const btn = document.getElementById('refresh-btn');
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.innerHTML = '<span class="animate-spin">↻</span> Lade...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/all');
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
updateDashboard(data);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Refresh failed:', e);
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.innerHTML = `
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
||||||
</svg>
|
|
||||||
<span>Refresh</span>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chat Functions
|
|
||||||
let chatWs = null;
|
|
||||||
let chatHistory = [];
|
|
||||||
|
|
||||||
function connectChatWebSocket() {
|
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
||||||
chatWs = new WebSocket(`${protocol}//${window.location.host}/ws/chat`);
|
|
||||||
|
|
||||||
chatWs.onopen = function() {
|
|
||||||
console.log('Chat WebSocket connected');
|
|
||||||
};
|
|
||||||
|
|
||||||
chatWs.onmessage = function(event) {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
|
|
||||||
if (data.type === 'history') {
|
|
||||||
// Clear and load history
|
|
||||||
document.getElementById('chat-messages').innerHTML = '';
|
|
||||||
data.messages.forEach(msg => {
|
|
||||||
addChatMessageToUI(msg.content, msg.role === 'user', msg.id);
|
|
||||||
});
|
|
||||||
// Add welcome message if empty
|
|
||||||
if (data.messages.length === 0) {
|
|
||||||
addChatMessageToUI("Hey! Ich bin Sam. Schreib mir hier direkt – ich antworte so schnell ich kann.", false, 'welcome');
|
|
||||||
}
|
|
||||||
} else if (data.type === 'message') {
|
|
||||||
const isUser = data.message.role === 'user';
|
|
||||||
addChatMessageToUI(data.message.content, isUser, data.message.id);
|
|
||||||
|
|
||||||
// Hide typing indicator for assistant messages
|
|
||||||
if (!isUser) {
|
|
||||||
document.getElementById('chat-typing').classList.add('hidden');
|
|
||||||
document.getElementById('chat-send-btn').disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
chatWs.onclose = function() {
|
|
||||||
console.log('Chat WebSocket disconnected, retrying...');
|
|
||||||
setTimeout(connectChatWebSocket, 5000);
|
|
||||||
};
|
|
||||||
|
|
||||||
chatWs.onerror = function(e) {
|
|
||||||
console.error('Chat WebSocket error:', e);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function addChatMessageToUI(text, isUser = false, id = '') {
|
|
||||||
const container = document.getElementById('chat-messages');
|
|
||||||
|
|
||||||
// Check if message already exists
|
|
||||||
if (id && document.getElementById(`msg-${id}`)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageDiv = document.createElement('div');
|
|
||||||
messageDiv.id = id ? `msg-${id}` : '';
|
|
||||||
messageDiv.className = 'flex items-start space-x-2 fade-in';
|
|
||||||
|
|
||||||
if (isUser) {
|
|
||||||
messageDiv.innerHTML = `
|
|
||||||
<div class="flex-1 flex justify-end">
|
|
||||||
<div class="bg-blue-600 rounded-lg px-3 py-2 text-sm text-white max-w-[80%]">
|
|
||||||
${escapeHtml(text)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-8 h-8 bg-gray-700 rounded-full flex items-center justify-center text-sm flex-shrink-0">👤</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
messageDiv.innerHTML = `
|
|
||||||
<div class="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-sm flex-shrink-0">🤖</div>
|
|
||||||
<div class="bg-gray-800 rounded-lg px-3 py-2 text-sm text-gray-200 max-w-[80%] whitespace-pre-wrap">
|
|
||||||
${escapeHtml(text)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
container.appendChild(messageDiv);
|
|
||||||
container.scrollTop = container.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(text) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.textContent = text;
|
|
||||||
return div.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendChatMessage() {
|
|
||||||
const input = document.getElementById('chat-input');
|
|
||||||
const btn = document.getElementById('chat-send-btn');
|
|
||||||
const text = input.value.trim();
|
|
||||||
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
// Check WebSocket connection
|
|
||||||
if (!chatWs || chatWs.readyState !== WebSocket.OPEN) {
|
|
||||||
addChatMessageToUI('Keine Verbindung zu Sam. Versuch es später nochmal.', false, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.value = '';
|
|
||||||
btn.disabled = true;
|
|
||||||
document.getElementById('chat-typing').classList.remove('hidden');
|
|
||||||
|
|
||||||
// Send via WebSocket
|
|
||||||
chatWs.send(JSON.stringify({
|
|
||||||
type: 'message',
|
|
||||||
content: text
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Re-enable button after timeout if no response
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.disabled = false;
|
|
||||||
document.getElementById('chat-typing').classList.add('hidden');
|
|
||||||
}, 30000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
connectChatWebSocket();
|
|
||||||
|
|
||||||
// Live Clock
|
// Live Clock
|
||||||
function updateClock() {
|
function updateClock() {
|
||||||
|
|
@ -790,12 +464,6 @@
|
||||||
}
|
}
|
||||||
setInterval(updateClock, 1000);
|
setInterval(updateClock, 1000);
|
||||||
updateClock();
|
updateClock();
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
||||||
manualRefresh();
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue