Update agents, dashboard, kb +2 more (+6 ~11)
This commit is contained in:
100
dashboard/api.py
100
dashboard/api.py
@@ -108,6 +108,8 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
self.handle_activity()
|
||||
elif self.path.startswith('/api/files'):
|
||||
self.handle_files_get()
|
||||
elif self.path.startswith('/api/diff'):
|
||||
self.handle_git_diff()
|
||||
elif self.path.startswith('/api/'):
|
||||
self.send_error(404)
|
||||
else:
|
||||
@@ -155,6 +157,14 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
cwd=workspace, capture_output=True, text=True, timeout=5
|
||||
).stdout.strip()
|
||||
|
||||
# Parse uncommitted into structured format
|
||||
uncommitted_parsed = []
|
||||
for line in uncommitted:
|
||||
if len(line) >= 3:
|
||||
status = line[:2].strip()
|
||||
filepath = line[3:].strip()
|
||||
uncommitted_parsed.append({'status': status, 'path': filepath})
|
||||
|
||||
self.send_json({
|
||||
'branch': branch,
|
||||
'lastCommit': {
|
||||
@@ -163,6 +173,7 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
'time': commit_parts[2] if len(commit_parts) > 2 else ''
|
||||
},
|
||||
'uncommitted': uncommitted,
|
||||
'uncommittedParsed': uncommitted_parsed,
|
||||
'uncommittedCount': len(uncommitted),
|
||||
'diffStat': diff_stat,
|
||||
'clean': len(uncommitted) == 0
|
||||
@@ -170,6 +181,60 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_git_diff(self):
|
||||
"""Get git diff for a specific file."""
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
parsed = urlparse(self.path)
|
||||
params = parse_qs(parsed.query)
|
||||
|
||||
filepath = params.get('path', [''])[0]
|
||||
|
||||
if not filepath:
|
||||
self.send_json({'error': 'path required'}, 400)
|
||||
return
|
||||
|
||||
try:
|
||||
workspace = Path('/home/moltbot/clawd')
|
||||
|
||||
# Security check
|
||||
target = (workspace / filepath).resolve()
|
||||
if not str(target).startswith(str(workspace)):
|
||||
self.send_json({'error': 'Access denied'}, 403)
|
||||
return
|
||||
|
||||
# Get diff (try staged first, then unstaged)
|
||||
diff = subprocess.run(
|
||||
['git', 'diff', '--cached', '--', filepath],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=10
|
||||
).stdout
|
||||
|
||||
if not diff:
|
||||
diff = subprocess.run(
|
||||
['git', 'diff', '--', filepath],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=10
|
||||
).stdout
|
||||
|
||||
# If still no diff, file might be untracked - show full content
|
||||
if not diff:
|
||||
status = subprocess.run(
|
||||
['git', 'status', '--short', '--', filepath],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=5
|
||||
).stdout.strip()
|
||||
|
||||
if status.startswith('??'):
|
||||
# Untracked file - show as new
|
||||
if target.exists():
|
||||
content = target.read_text(encoding='utf-8', errors='replace')[:50000]
|
||||
diff = f"+++ b/{filepath}\n" + '\n'.join(f'+{line}' for line in content.split('\n'))
|
||||
|
||||
self.send_json({
|
||||
'path': filepath,
|
||||
'diff': diff or 'No changes',
|
||||
'hasDiff': bool(diff)
|
||||
})
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_agents_status(self):
|
||||
"""Get agents status - fast version reading session files directly."""
|
||||
try:
|
||||
@@ -361,11 +426,44 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
'text': message[:60] + ('...' if len(message) > 60 else ''),
|
||||
'agent': 'git',
|
||||
'time': local_time.strftime('%H:%M'),
|
||||
'timestamp': timestamp * 1000
|
||||
'timestamp': timestamp * 1000,
|
||||
'commitHash': commit_hash[:8]
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
# 2b. Git uncommitted files
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['git', 'status', '--short'],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if len(line) >= 4:
|
||||
# Git status format: XY filename (XY = 2 chars status)
|
||||
# Handle both "M " and " M" formats
|
||||
status = line[:2]
|
||||
# Find filepath - skip status chars and any spaces
|
||||
filepath = line[2:].lstrip()
|
||||
if not filepath:
|
||||
continue
|
||||
status_clean = status.strip()
|
||||
status_labels = {'M': 'modificat', 'A': 'adăugat', 'D': 'șters', '??': 'nou', 'R': 'redenumit'}
|
||||
status_label = status_labels.get(status_clean, status_clean)
|
||||
activities.append({
|
||||
'type': 'git-file',
|
||||
'icon': 'file-diff',
|
||||
'text': f"{filepath}",
|
||||
'agent': f"git ({status_label})",
|
||||
'time': 'acum',
|
||||
'timestamp': int(datetime.now().timestamp() * 1000),
|
||||
'path': filepath,
|
||||
'gitStatus': status_clean
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
# 3. Recent files in kb/ (last 24h)
|
||||
try:
|
||||
kb_dir = workspace / 'kb'
|
||||
|
||||
4
dashboard/favicon.svg
Normal file
4
dashboard/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<circle cx="16" cy="16" r="14" fill="none" stroke="#3b82f6" stroke-width="2.5"/>
|
||||
<circle cx="16" cy="16" r="3" fill="#3b82f6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 200 B |
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<title>Echo · Files</title>
|
||||
<link rel="stylesheet" href="common.css">
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||
@@ -294,6 +295,41 @@
|
||||
background: var(--accent-subtle);
|
||||
}
|
||||
|
||||
.file-item.git-changed {
|
||||
border-left: 3px solid var(--warning);
|
||||
}
|
||||
|
||||
.git-badge {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
margin-right: 4px;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.git-modified { background: #3b82f620; color: #3b82f6; }
|
||||
.git-added { background: #22c55e20; color: #22c55e; }
|
||||
.git-deleted { background: #ef444420; color: #ef4444; }
|
||||
.git-untracked { background: #f59e0b20; color: #f59e0b; }
|
||||
.git-renamed { background: #8b5cf620; color: #8b5cf6; }
|
||||
|
||||
.diff-btn {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.diff-btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
@@ -419,6 +455,9 @@
|
||||
.preview-active #markdownPreview { display: block; }
|
||||
|
||||
.btn-preview.active { background: var(--accent); color: white; }
|
||||
.btn-diff.active { background: var(--warning); color: white; }
|
||||
|
||||
#gitFilterBtn.active { background: var(--warning); color: white; }
|
||||
|
||||
.editor-footer {
|
||||
padding: var(--space-2) var(--space-5);
|
||||
@@ -493,6 +532,13 @@
|
||||
<span class="breadcrumb-item current" onclick="loadPath('')">~/clawd</span>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<!-- Git Filter Toggle -->
|
||||
<div class="view-toggle">
|
||||
<button class="view-btn" id="gitFilterBtn" onclick="toggleGitFilter()" title="Git Changes">
|
||||
<i data-lucide="git-branch"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- View Mode Toggle -->
|
||||
<div class="view-toggle" id="viewModeToggle">
|
||||
<button class="view-btn" data-view="list" onclick="setViewMode('list')" title="Listă">
|
||||
@@ -554,6 +600,9 @@
|
||||
<button class="btn btn-ghost btn-preview" onclick="togglePreview()" id="previewBtn" style="display:none;" title="Preview Markdown">
|
||||
<i data-lucide="eye"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-diff" onclick="toggleDiff()" id="diffBtn" style="display:none;" title="Git Diff">
|
||||
<i data-lucide="git-compare"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick="reloadFile()" id="reloadBtn" disabled title="Reload">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
@@ -611,6 +660,8 @@
|
||||
let currentSortBy = localStorage.getItem('filesSortBy') || 'name';
|
||||
let currentSortDir = localStorage.getItem('filesSortDir') || 'asc';
|
||||
let currentItems = [];
|
||||
let gitStatus = {}; // Map of filepath -> status (M, A, D, ??)
|
||||
let gitOnlyMode = false; // Show only git-changed files
|
||||
|
||||
// Initialize view mode
|
||||
function initViewMode() {
|
||||
@@ -690,6 +741,63 @@
|
||||
document.getElementById('editorBtn').classList.add('active');
|
||||
}
|
||||
|
||||
async function loadGitStatus() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/git?` + Date.now());
|
||||
const data = await response.json();
|
||||
gitStatus = {};
|
||||
if (data.uncommittedParsed) {
|
||||
data.uncommittedParsed.forEach(item => {
|
||||
gitStatus[item.path] = item.status;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load git status:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function showDiff(filepath, event) {
|
||||
if (event) event.stopPropagation();
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/diff?path=${encodeURIComponent(filepath)}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Show in a modal or the editor
|
||||
const diffHtml = data.diff
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
if (line.startsWith('+') && !line.startsWith('+++')) {
|
||||
return `<span style="color:var(--success)">${escapeHtml(line)}</span>`;
|
||||
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
||||
return `<span style="color:var(--error)">${escapeHtml(line)}</span>`;
|
||||
} else if (line.startsWith('@@')) {
|
||||
return `<span style="color:var(--accent)">${escapeHtml(line)}</span>`;
|
||||
}
|
||||
return escapeHtml(line);
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
// Open file in editor with diff view
|
||||
document.getElementById('editorFileName').textContent = `DIFF: ${filepath}`;
|
||||
document.getElementById('codeEditor').value = data.diff;
|
||||
document.getElementById('markdownPreview').innerHTML = `<pre style="font-family:var(--font-mono);font-size:13px;line-height:1.5">${diffHtml}</pre>`;
|
||||
document.getElementById('editorBody').classList.add('preview-active');
|
||||
document.getElementById('previewBtn').style.display = 'flex';
|
||||
document.getElementById('previewBtn').classList.add('active');
|
||||
document.getElementById('saveBtn').disabled = true;
|
||||
document.getElementById('reloadBtn').disabled = true;
|
||||
currentFile = null;
|
||||
setStatus('Diff view', 'saved');
|
||||
showEditor();
|
||||
} catch (e) {
|
||||
alert('Eroare la încărcare diff: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
async function loadPath(path = '') {
|
||||
currentPath = path;
|
||||
updateBreadcrumb();
|
||||
@@ -704,6 +812,7 @@
|
||||
}
|
||||
|
||||
if (data.type === 'dir') {
|
||||
await loadGitStatus(); // Refresh git status
|
||||
renderFileGrid(data.items);
|
||||
updateURL(path);
|
||||
} else if (data.type === 'file') {
|
||||
@@ -796,37 +905,42 @@
|
||||
const fileType = item.type === 'dir' ? 'Folder' : getFileType(item.name);
|
||||
const sizeStr = item.size !== undefined ? formatSize(item.size) : '-';
|
||||
|
||||
// Git status
|
||||
const gStatus = gitStatus[item.path] || '';
|
||||
const gitBadge = gStatus ? getGitBadge(gStatus) : '';
|
||||
const hasGitChange = !!gStatus;
|
||||
|
||||
if (currentViewMode === 'details') {
|
||||
return `
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''} ${hasGitChange ? 'git-changed' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
|
||||
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
<div class="file-name">${gitBadge}${item.name}</div>
|
||||
<div class="file-meta">
|
||||
<span class="file-type">${fileType}</span>
|
||||
<span class="file-size">${sizeStr}</span>
|
||||
<span class="file-date">${dateStr || '-'}</span>
|
||||
<span class="file-date">${hasGitChange ? `<button class="diff-btn" onclick="showDiff('${item.path}', event)" title="Vezi diff">diff</button>` : (dateStr || '-')}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (currentViewMode === 'list') {
|
||||
return `
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''} ${hasGitChange ? 'git-changed' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
|
||||
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
<div class="file-name">${gitBadge}${item.name}</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Tiles view - original style
|
||||
return `
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''} ${hasGitChange ? 'git-changed' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
|
||||
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
<div class="file-name">${gitBadge}${item.name}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -881,6 +995,17 @@
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
function getGitBadge(status) {
|
||||
const badges = {
|
||||
'M': '<span class="git-badge git-modified" title="Modificat">M</span>',
|
||||
'A': '<span class="git-badge git-added" title="Adăugat">A</span>',
|
||||
'D': '<span class="git-badge git-deleted" title="Șters">D</span>',
|
||||
'??': '<span class="git-badge git-untracked" title="Nou (untracked)">+</span>',
|
||||
'R': '<span class="git-badge git-renamed" title="Redenumit">R</span>',
|
||||
};
|
||||
return badges[status] || `<span class="git-badge">${status}</span>`;
|
||||
}
|
||||
|
||||
function handleClick(path, type) {
|
||||
if (type === 'dir') {
|
||||
loadPath(path);
|
||||
@@ -905,6 +1030,10 @@
|
||||
const isMarkdown = path.endsWith('.md');
|
||||
document.getElementById('previewBtn').style.display = isMarkdown ? 'flex' : 'none';
|
||||
|
||||
// Always show diff button - let user check if file has changes
|
||||
document.getElementById('diffBtn').style.display = 'flex';
|
||||
document.getElementById('diffBtn').classList.remove('active');
|
||||
|
||||
// Auto-activate preview for markdown files
|
||||
if (isMarkdown) {
|
||||
const preview = document.getElementById('markdownPreview');
|
||||
@@ -929,6 +1058,7 @@
|
||||
function togglePreview() {
|
||||
const editorBody = document.getElementById('editorBody');
|
||||
const previewBtn = document.getElementById('previewBtn');
|
||||
const diffBtn = document.getElementById('diffBtn');
|
||||
const preview = document.getElementById('markdownPreview');
|
||||
const content = document.getElementById('codeEditor').value;
|
||||
|
||||
@@ -936,16 +1066,69 @@
|
||||
// Switch to edit mode
|
||||
editorBody.classList.remove('preview-active');
|
||||
previewBtn.classList.remove('active');
|
||||
if (diffBtn) diffBtn.classList.remove('active');
|
||||
setStatus('Edit mode', 'saved');
|
||||
} else {
|
||||
// Switch to preview mode
|
||||
preview.innerHTML = marked.parse(content);
|
||||
editorBody.classList.add('preview-active');
|
||||
previewBtn.classList.add('active');
|
||||
if (diffBtn) diffBtn.classList.remove('active');
|
||||
setStatus('Preview mode', 'saved');
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleDiff() {
|
||||
if (!currentFile) return;
|
||||
|
||||
const editorBody = document.getElementById('editorBody');
|
||||
const diffBtn = document.getElementById('diffBtn');
|
||||
const previewBtn = document.getElementById('previewBtn');
|
||||
const preview = document.getElementById('markdownPreview');
|
||||
|
||||
// If already showing diff, switch back to edit
|
||||
if (diffBtn.classList.contains('active')) {
|
||||
editorBody.classList.remove('preview-active');
|
||||
diffBtn.classList.remove('active');
|
||||
setStatus('Edit mode', 'saved');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setStatus('Se încarcă diff...', 'modified');
|
||||
const response = await fetch(`${API_BASE}/api/diff?path=${encodeURIComponent(currentFile)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
setStatus('Eroare: ' + data.error, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format diff with colors
|
||||
const diffHtml = data.diff
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
if (line.startsWith('+') && !line.startsWith('+++')) {
|
||||
return `<span style="color:var(--success)">${escapeHtml(line)}</span>`;
|
||||
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
||||
return `<span style="color:var(--error)">${escapeHtml(line)}</span>`;
|
||||
} else if (line.startsWith('@@')) {
|
||||
return `<span style="color:var(--accent)">${escapeHtml(line)}</span>`;
|
||||
}
|
||||
return escapeHtml(line);
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
preview.innerHTML = `<pre style="font-family:var(--font-mono);font-size:13px;line-height:1.5;white-space:pre-wrap">${diffHtml || 'Nicio modificare față de ultima versiune comisă.'}</pre>`;
|
||||
editorBody.classList.add('preview-active');
|
||||
diffBtn.classList.add('active');
|
||||
if (previewBtn) previewBtn.classList.remove('active');
|
||||
setStatus('Diff view', 'saved');
|
||||
} catch (e) {
|
||||
setStatus('Eroare: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFile() {
|
||||
if (!currentFile) return;
|
||||
|
||||
@@ -1028,7 +1211,68 @@
|
||||
|
||||
// Init
|
||||
initViewMode();
|
||||
loadPath(getPathFromURL());
|
||||
|
||||
// Check for git mode
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('git') === '1') {
|
||||
gitOnlyMode = true;
|
||||
loadGitChangedFiles();
|
||||
} else {
|
||||
loadPath(getPathFromURL());
|
||||
}
|
||||
|
||||
function toggleGitFilter() {
|
||||
gitOnlyMode = !gitOnlyMode;
|
||||
const btn = document.getElementById('gitFilterBtn');
|
||||
btn.classList.toggle('active', gitOnlyMode);
|
||||
|
||||
if (gitOnlyMode) {
|
||||
loadGitChangedFiles();
|
||||
} else {
|
||||
// Return to normal browse
|
||||
window.history.replaceState(null, '', 'files.html');
|
||||
loadPath('');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGitChangedFiles() {
|
||||
await loadGitStatus();
|
||||
const changedPaths = Object.keys(gitStatus);
|
||||
|
||||
// Update button state
|
||||
document.getElementById('gitFilterBtn').classList.add('active');
|
||||
|
||||
if (changedPaths.length === 0) {
|
||||
document.getElementById('breadcrumb').innerHTML = `
|
||||
<span class="breadcrumb-item" onclick="toggleGitFilter()">~/clawd</span>
|
||||
<span class="breadcrumb-sep"><i data-lucide="chevron-right"></i></span>
|
||||
<span class="breadcrumb-item current" style="color:var(--success)">✓ Git curat</span>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
showError('Nicio modificare git - totul e comis!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update breadcrumb
|
||||
document.getElementById('breadcrumb').innerHTML = `
|
||||
<span class="breadcrumb-item" onclick="toggleGitFilter()">~/clawd</span>
|
||||
<span class="breadcrumb-sep"><i data-lucide="chevron-right"></i></span>
|
||||
<span class="breadcrumb-item current" style="color:var(--warning)">🔸 Git Changes (${changedPaths.length})</span>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
|
||||
// Create virtual items for changed files
|
||||
const items = changedPaths.map(path => ({
|
||||
name: path,
|
||||
path: path,
|
||||
type: 'file',
|
||||
size: null,
|
||||
mtime: null
|
||||
}));
|
||||
|
||||
currentItems = items;
|
||||
renderFileGrid(items);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<title>Echo · Dashboard</title>
|
||||
<link rel="stylesheet" href="common.css">
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||
@@ -14,61 +15,6 @@
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
/* Stats Summary */
|
||||
.stats-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stats-summary {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-subtle);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.stat-icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
@@ -518,6 +464,11 @@
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.activity-icon.git-file {
|
||||
background: rgba(234, 179, 8, 0.2);
|
||||
color: #eab308;
|
||||
}
|
||||
|
||||
.activity-icon.file {
|
||||
background: rgba(20, 184, 166, 0.2);
|
||||
color: #14b8a6;
|
||||
@@ -1000,38 +951,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Summary -->
|
||||
<div class="stats-summary" id="statsSummary">
|
||||
<div class="stat-card" title="Task-uri completate azi">
|
||||
<div class="stat-icon"><i data-lucide="check-circle"></i></div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value" id="statToday">0</div>
|
||||
<div class="stat-label">Tasks azi</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card" title="Task-uri completate săptămâna aceasta">
|
||||
<div class="stat-icon"><i data-lucide="calendar"></i></div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value" id="statWeek">0</div>
|
||||
<div class="stat-label">Tasks săpt.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card" title="Task-uri completate luna aceasta">
|
||||
<div class="stat-icon"><i data-lucide="trending-up"></i></div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value" id="statMonth">0</div>
|
||||
<div class="stat-label">Tasks lună</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card" title="Număr de fișiere insights">
|
||||
<div class="stat-icon"><i data-lucide="lightbulb"></i></div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value" id="statInsights">0</div>
|
||||
<div class="stat-label">Insights</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<!-- Activity Panel -->
|
||||
<div class="panel activity-panel" id="activityPanel">
|
||||
@@ -1297,40 +1216,28 @@
|
||||
|
||||
// Update details
|
||||
const details = document.getElementById('gitDetails');
|
||||
const GITEA_URL = 'https://gitea.romfast.ro/romfast/clawd';
|
||||
let html = `
|
||||
<div class="status-detail-item">
|
||||
<i data-lucide="git-branch"></i>
|
||||
<span>Branch: <strong>${git.branch}</strong></span>
|
||||
</div>
|
||||
<div class="status-detail-item">
|
||||
<i data-lucide="git-commit"></i>
|
||||
<span>Last: <code>${git.lastCommit.hash}</code> ${git.lastCommit.message} (${git.lastCommit.time})</span>
|
||||
<span><a href="${GITEA_URL}/commit/${git.lastCommit.hash}" target="_blank" style="color:var(--accent)">${git.lastCommit.hash}</a> ${git.lastCommit.message.substring(0, 40)}${git.lastCommit.message.length > 40 ? '...' : ''} <small>(${git.lastCommit.time})</small></span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (git.uncommittedCount > 0) {
|
||||
const files = git.uncommitted.slice(0, 3).map(f => f.trim().split(' ').pop()).join(', ');
|
||||
const more = git.uncommittedCount > 3 ? ` +${git.uncommittedCount - 3}` : '';
|
||||
html += `<div class="status-detail-item uncommitted">
|
||||
<i data-lucide="alert-circle"></i>
|
||||
<span><strong>${git.uncommittedCount}</strong> fișiere necomise:</span>
|
||||
</div>`;
|
||||
git.uncommitted.slice(0, 5).forEach(file => {
|
||||
html += `<div class="status-detail-item uncommitted">
|
||||
<i data-lucide="file"></i>
|
||||
<code>${file}</code>
|
||||
</div>`;
|
||||
});
|
||||
if (git.uncommittedCount > 5) {
|
||||
html += `<div class="status-detail-item uncommitted">
|
||||
<span>... și încă ${git.uncommittedCount - 5}</span>
|
||||
</div>`;
|
||||
}
|
||||
} else {
|
||||
html += `<div class="status-detail-item">
|
||||
<i data-lucide="check-circle"></i>
|
||||
<span>Totul comis ✓</span>
|
||||
<span><a href="files.html?git=1" style="color:var(--warning)"><strong>${git.uncommittedCount}</strong> necomise</a>: <small>${files}${more}</small></span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
html += `<div class="status-detail-item">
|
||||
<i data-lucide="external-link"></i>
|
||||
<a href="${GITEA_URL}" target="_blank" style="color:var(--accent);font-size:var(--text-xs)">gitea.romfast.ro/romfast/clawd</a>
|
||||
</div>`;
|
||||
|
||||
details.innerHTML = html;
|
||||
lucide.createIcons();
|
||||
|
||||
@@ -1354,7 +1261,9 @@
|
||||
badge.className = 'status-badge ' + (status.anaf.ok !== false ? 'ok' : 'warning');
|
||||
|
||||
const subtitle = document.getElementById('anafSubtitle');
|
||||
subtitle.textContent = status.anaf.message || 'Nicio modificare detectată';
|
||||
const lastCheck = status.anaf.lastCheck || '-';
|
||||
const msg = status.anaf.ok !== false ? 'Nicio modificare' : (status.anaf.message || 'Modificări!');
|
||||
subtitle.textContent = `${msg} · ${lastCheck}`;
|
||||
|
||||
if (status.anaf.lastCheck) {
|
||||
document.getElementById('anafLastCheck').textContent =
|
||||
@@ -1542,51 +1451,9 @@
|
||||
|
||||
function refreshActivity() {
|
||||
loadActivity();
|
||||
loadStats();
|
||||
showToast('Activitate reîmprospătată');
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
// Load tasks
|
||||
const tasksRes = await fetch('tasks.json');
|
||||
const tasksData = await tasksRes.json();
|
||||
|
||||
// Get done tasks
|
||||
const doneColumn = tasksData.columns.find(c => c.id === 'done');
|
||||
const doneTasks = doneColumn ? doneColumn.tasks : [];
|
||||
|
||||
const now = new Date();
|
||||
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const weekStart = new Date(todayStart);
|
||||
weekStart.setDate(weekStart.getDate() - weekStart.getDay() + 1); // Monday
|
||||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
|
||||
let today = 0, week = 0, month = 0;
|
||||
|
||||
doneTasks.forEach(task => {
|
||||
if (!task.completed) return;
|
||||
const completed = new Date(task.completed);
|
||||
if (completed >= todayStart) today++;
|
||||
if (completed >= weekStart) week++;
|
||||
if (completed >= monthStart) month++;
|
||||
});
|
||||
|
||||
document.getElementById('statToday').textContent = today;
|
||||
document.getElementById('statWeek').textContent = week;
|
||||
document.getElementById('statMonth').textContent = month;
|
||||
|
||||
// Count insights
|
||||
const insightsRes = await fetch('/echo/api/files?path=kb/insights');
|
||||
if (insightsRes.ok) {
|
||||
const insightsData = await insightsRes.json();
|
||||
document.getElementById('statInsights').textContent = insightsData.files ? insightsData.files.length : 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Stats load error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function renderActivity() {
|
||||
const body = document.getElementById('activityBody');
|
||||
|
||||
@@ -1607,6 +1474,7 @@
|
||||
const typeLabels = {
|
||||
'cron': '⏰ Cron Jobs',
|
||||
'git': '📦 Git Commits',
|
||||
'git-file': '🔸 Git Changes',
|
||||
'file': '📄 Fișiere',
|
||||
'task': '✅ Task-uri'
|
||||
};
|
||||
@@ -1617,13 +1485,22 @@
|
||||
<i data-lucide="activity"></i>
|
||||
Ultimele 24h
|
||||
</div>
|
||||
${activityData.map(item => `
|
||||
<div class="activity-item" ${item.path ? `onclick="window.open('files.html#${item.path}', '_blank')" style="cursor:pointer"` : ''}>
|
||||
${activityData.map(item => {
|
||||
let clickAttr = '';
|
||||
if (item.type === 'git-file' && item.path) {
|
||||
clickAttr = `onclick="window.open('files.html#${item.path}', '_blank')" style="cursor:pointer"`;
|
||||
} else if (item.path) {
|
||||
clickAttr = `onclick="window.open('files.html#${item.path}', '_blank')" style="cursor:pointer"`;
|
||||
} else if (item.type === 'git' && item.commitHash) {
|
||||
clickAttr = `onclick="window.open('https://gitea.romfast.ro/romfast/clawd/commit/${item.commitHash}', '_blank')" style="cursor:pointer"`;
|
||||
}
|
||||
return `
|
||||
<div class="activity-item" ${clickAttr}>
|
||||
<div class="activity-icon ${item.type}">
|
||||
<i data-lucide="${item.icon || 'activity'}"></i>
|
||||
</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-text">${item.text}</div>
|
||||
<div class="activity-text">${item.type === 'git' && item.commitHash ? `<code style="font-size:10px;margin-right:4px">${item.commitHash}</code>` : ''}${item.text}</div>
|
||||
<div class="activity-meta">
|
||||
<span class="activity-type">${typeLabels[item.type] || item.type}</span>
|
||||
<span class="activity-agent">${item.agent}</span>
|
||||
@@ -1631,7 +1508,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
`}).join('')}
|
||||
</div>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
@@ -1867,7 +1744,6 @@
|
||||
loadStatus();
|
||||
loadIssues();
|
||||
loadActivity();
|
||||
loadStats();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<title>Echo · KB</title>
|
||||
<link rel="stylesheet" href="common.css">
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||
@@ -1203,7 +1204,7 @@
|
||||
try {
|
||||
let content = notesCache[file];
|
||||
if (!content) {
|
||||
const response = await fetch(file);
|
||||
const response = await fetch(file + '?t=' + Date.now());
|
||||
content = await response.text();
|
||||
notesCache[file] = content;
|
||||
}
|
||||
@@ -1222,7 +1223,7 @@
|
||||
async function preloadNotes() {
|
||||
for (const note of notesIndex) {
|
||||
try {
|
||||
const response = await fetch(note.file);
|
||||
const response = await fetch(note.file + '?t=' + Date.now());
|
||||
notesCache[note.file] = await response.text();
|
||||
} catch (e) {
|
||||
notesCache[note.file] = '';
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
{
|
||||
"git": {"status": "4 fișiere", "clean": false, "files": 4},
|
||||
"lastReport": {"type": "evening", "summary": "notes.html îmbunătățit (filtre colorate), rețetă salvată", "time": "30 Jan 2026, 22:00"}
|
||||
}
|
||||
"git": {
|
||||
"status": "4 fișiere",
|
||||
"clean": false,
|
||||
"files": 4
|
||||
},
|
||||
"lastReport": {
|
||||
"type": "evening",
|
||||
"summary": "notes.html îmbunătățit (filtre colorate), rețetă salvată",
|
||||
"time": "30 Jan 2026, 22:00"
|
||||
},
|
||||
"anaf": {
|
||||
"ok": true,
|
||||
"status": "OK",
|
||||
"message": "Nicio modificare detectată",
|
||||
"lastCheck": "31 Jan 2026, 13:43",
|
||||
"changesCount": 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user