150 lines
4.3 KiB
HTML
150 lines
4.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="sr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>KAOS Dashboard</title>
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
<script src="/static/htmx.min.js"></script>
|
|
<script src="/static/sortable.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>🔧 KAOS Dashboard</h1>
|
|
<div class="header-right">
|
|
<div class="search-wrapper">
|
|
<input type="search" id="search-input"
|
|
hx-get="/search"
|
|
hx-trigger="keyup changed delay:300ms"
|
|
hx-target="#search-results"
|
|
name="q"
|
|
placeholder="Pretraži..."
|
|
autocomplete="off">
|
|
<div id="search-results" class="search-results-dropdown"></div>
|
|
</div>
|
|
<nav class="nav">
|
|
<a href="/" class="btn btn-active">Kanban</a>
|
|
<a href="/docs" class="btn">Dokumenti</a>
|
|
<a href="/console" class="btn">Konzola</a>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
{{block "content" .}}{{end}}
|
|
<div id="task-detail"></div>
|
|
<div id="toast" class="toast"></div>
|
|
<script>
|
|
document.body.addEventListener('htmx:afterSwap', function(e) {
|
|
if (e.detail.target.id === 'task-detail') {
|
|
e.detail.target.classList.add('active');
|
|
}
|
|
});
|
|
|
|
function closeDetail() {
|
|
var el = document.getElementById('task-detail');
|
|
el.classList.remove('active');
|
|
el.innerHTML = '';
|
|
}
|
|
|
|
function showToast(msg, type) {
|
|
var toast = document.getElementById('toast');
|
|
toast.textContent = msg;
|
|
toast.className = 'toast toast-' + type + ' toast-show';
|
|
setTimeout(function() {
|
|
toast.className = 'toast';
|
|
}, 2000);
|
|
}
|
|
|
|
// Handle "Pusti" button response
|
|
document.body.addEventListener('htmx:afterRequest', function(e) {
|
|
if (e.detail.pathInfo && e.detail.pathInfo.requestPath && e.detail.pathInfo.requestPath.match(/\/task\/T\d+\/run/)) {
|
|
var xhr = e.detail.xhr;
|
|
if (xhr.status === 200) {
|
|
var data = JSON.parse(xhr.responseText);
|
|
showToast(data.exec_id ? 'Pokrenuto u sesiji ' + data.session : 'Pokrenuto', 'success');
|
|
} else {
|
|
var data = JSON.parse(xhr.responseText);
|
|
showToast(data.error || 'Greška', 'error');
|
|
}
|
|
htmx.ajax('GET', '/', {target: '#board', swap: 'outerHTML', select: '#board'});
|
|
}
|
|
});
|
|
|
|
var isDragging = false;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initSortable();
|
|
initSSE();
|
|
|
|
// Close search results on click outside
|
|
document.addEventListener('click', function(e) {
|
|
var wrapper = document.querySelector('.search-wrapper');
|
|
var results = document.getElementById('search-results');
|
|
if (wrapper && !wrapper.contains(e.target)) {
|
|
results.innerHTML = '';
|
|
}
|
|
});
|
|
});
|
|
|
|
function initSSE() {
|
|
var source = new EventSource('/events');
|
|
source.addEventListener('taskUpdate', function(e) {
|
|
if (isDragging) return;
|
|
var board = document.getElementById('board');
|
|
if (board) {
|
|
board.outerHTML = e.data;
|
|
initSortable();
|
|
}
|
|
});
|
|
source.onerror = function() {
|
|
// EventSource auto-reconnects
|
|
};
|
|
}
|
|
|
|
document.body.addEventListener('htmx:afterSwap', function(e) {
|
|
if (e.detail.target.id === 'board') {
|
|
initSortable();
|
|
}
|
|
});
|
|
|
|
function initSortable() {
|
|
document.querySelectorAll('.column-tasks').forEach(function(col) {
|
|
new Sortable(col, {
|
|
group: 'tasks',
|
|
animation: 150,
|
|
ghostClass: 'task-ghost',
|
|
chosenClass: 'task-chosen',
|
|
dragClass: 'task-drag',
|
|
filter: '.column-header',
|
|
onStart: function() { isDragging = true; },
|
|
onEnd: function(evt) {
|
|
isDragging = false;
|
|
var taskId = evt.item.dataset.id;
|
|
var toFolder = evt.to.dataset.folder;
|
|
var fromFolder = evt.from.dataset.folder;
|
|
|
|
if (fromFolder === toFolder) return;
|
|
|
|
fetch('/api/task/' + taskId + '/move?to=' + toFolder, {
|
|
method: 'POST'
|
|
})
|
|
.then(function(resp) {
|
|
if (!resp.ok) {
|
|
return resp.json().then(function(data) {
|
|
throw new Error(data.error || 'Greška');
|
|
});
|
|
}
|
|
showToast(taskId + ' → ' + toFolder, 'success');
|
|
htmx.ajax('GET', '/', {target: '#board', swap: 'outerHTML', select: '#board'});
|
|
})
|
|
.catch(function(err) {
|
|
showToast(err.message, 'error');
|
|
htmx.ajax('GET', '/', {target: '#board', swap: 'outerHTML', select: '#board'});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|