Status bar v2: expandable sections with live Git status
- Git section: branch, last commit, uncommitted files with API - ANAF section: collapsible with last check time - Cron section: shows today's jobs with done/pending status - Refresh button + auto-refresh on page focus - New /api/git endpoint for live git status - All sections collapsible with localStorage persistence
This commit is contained in:
@@ -63,6 +63,8 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == '/api/status':
|
||||
self.send_json({'status': 'ok', 'time': datetime.now().isoformat()})
|
||||
elif self.path == '/api/git':
|
||||
self.handle_git_status()
|
||||
elif self.path.startswith('/api/files'):
|
||||
self.handle_files_get()
|
||||
elif self.path.startswith('/api/'):
|
||||
@@ -71,6 +73,62 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
# Serve static files
|
||||
super().do_GET()
|
||||
|
||||
def handle_git_status(self):
|
||||
"""Get git status for dashboard."""
|
||||
try:
|
||||
workspace = Path('/home/moltbot/clawd')
|
||||
|
||||
# Get current branch
|
||||
branch = subprocess.run(
|
||||
['git', 'branch', '--show-current'],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=5
|
||||
).stdout.strip()
|
||||
|
||||
# Get last commit
|
||||
last_commit = subprocess.run(
|
||||
['git', 'log', '-1', '--format=%h|%s|%cr'],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=5
|
||||
).stdout.strip()
|
||||
|
||||
commit_parts = last_commit.split('|') if last_commit else ['', '', '']
|
||||
|
||||
# Get uncommitted files
|
||||
status_output = subprocess.run(
|
||||
['git', 'status', '--short'],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=5
|
||||
).stdout.strip()
|
||||
|
||||
uncommitted = status_output.split('\n') if status_output else []
|
||||
uncommitted = [f for f in uncommitted if f.strip()]
|
||||
|
||||
# Get diff stats if there are uncommitted files
|
||||
diff_stat = ''
|
||||
if uncommitted:
|
||||
diff_stat = subprocess.run(
|
||||
['git', 'diff', '--stat', '--cached'],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=5
|
||||
).stdout.strip()
|
||||
if not diff_stat:
|
||||
diff_stat = subprocess.run(
|
||||
['git', 'diff', '--stat'],
|
||||
cwd=workspace, capture_output=True, text=True, timeout=5
|
||||
).stdout.strip()
|
||||
|
||||
self.send_json({
|
||||
'branch': branch,
|
||||
'lastCommit': {
|
||||
'hash': commit_parts[0] if len(commit_parts) > 0 else '',
|
||||
'message': commit_parts[1] if len(commit_parts) > 1 else '',
|
||||
'time': commit_parts[2] if len(commit_parts) > 2 else ''
|
||||
},
|
||||
'uncommitted': uncommitted,
|
||||
'uncommittedCount': len(uncommitted),
|
||||
'diffStat': diff_stat,
|
||||
'clean': len(uncommitted) == 0
|
||||
})
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_files_get(self):
|
||||
"""List files or get file content."""
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
@@ -69,6 +69,12 @@
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.status-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.status-summary {
|
||||
flex: 1;
|
||||
font-size: var(--text-xs);
|
||||
@@ -92,64 +98,186 @@
|
||||
}
|
||||
|
||||
.status-content {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-2);
|
||||
/* Status sections */
|
||||
.status-section {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.status-item {
|
||||
.status-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.status-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.status-section-header:hover {
|
||||
background: var(--bg-elevated);
|
||||
}
|
||||
|
||||
.status-section-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-section-icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.status-section-icon.git {
|
||||
background: rgba(249, 115, 22, 0.15);
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.status-section-icon.anaf {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.status-section-icon.cron {
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
color: #818cf8;
|
||||
}
|
||||
|
||||
.status-section-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.status-section-title {
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.status-label {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.status-value.ok { color: #22c55e; }
|
||||
.status-value.warning { color: #f59e0b; }
|
||||
.status-value.error { color: #ef4444; }
|
||||
|
||||
.status-time {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
margin-left: var(--space-1);
|
||||
}
|
||||
|
||||
.cron-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-sm);
|
||||
padding-top: var(--space-2);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.cron-label {
|
||||
.status-section-subtitle {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-badge.ok {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.status-badge.warning {
|
||||
background: rgba(249, 115, 22, 0.15);
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.status-badge.error {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.status-section-toggle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-muted);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.status-section.collapsed .status-section-toggle {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.status-section.collapsed .status-section-details {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-section-details {
|
||||
padding: 0 var(--space-4) var(--space-3);
|
||||
padding-left: calc(var(--space-4) + 32px + var(--space-3));
|
||||
}
|
||||
|
||||
.status-detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-secondary);
|
||||
padding: var(--space-1) 0;
|
||||
}
|
||||
|
||||
.status-detail-item svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.cron-list {
|
||||
.status-detail-item.uncommitted {
|
||||
color: #f97316;
|
||||
}
|
||||
|
||||
.status-detail-item code {
|
||||
font-family: monospace;
|
||||
background: var(--bg-elevated);
|
||||
padding: 1px 4px;
|
||||
border-radius: 2px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Cron items */
|
||||
.cron-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
padding: var(--space-1) 0;
|
||||
}
|
||||
|
||||
.cron-item.done {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.cron-item.done .cron-name {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.cron-item.pending {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.cron-done {
|
||||
color: var(--text-muted);
|
||||
text-decoration: line-through;
|
||||
.cron-time {
|
||||
font-family: monospace;
|
||||
min-width: 45px;
|
||||
}
|
||||
|
||||
.cron-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.cron-icon.done { color: #22c55e; }
|
||||
.cron-icon.pending { color: var(--text-muted); }
|
||||
.cron-icon.failed { color: #ef4444; }
|
||||
|
||||
/* Two-column dashboard */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
@@ -696,27 +824,75 @@
|
||||
<span>Status</span>
|
||||
</div>
|
||||
<div class="status-summary" id="statusSummary">Se încarcă...</div>
|
||||
<div class="status-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-secondary btn-sm" onclick="refreshStatus()" title="Refresh">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-toggle"></i>
|
||||
</div>
|
||||
<div class="status-content">
|
||||
<div class="status-row">
|
||||
<div class="status-item">
|
||||
<span class="status-label">ANAF:</span>
|
||||
<span class="status-value" id="anafStatus">-</span>
|
||||
<!-- Git Section -->
|
||||
<div class="status-section" id="gitSection">
|
||||
<div class="status-section-header" onclick="toggleStatusSection('gitSection')">
|
||||
<div class="status-section-icon git">
|
||||
<i data-lucide="git-branch"></i>
|
||||
</div>
|
||||
<div class="status-section-info">
|
||||
<div class="status-section-title">
|
||||
Git
|
||||
<span class="status-badge ok" id="gitBadge">curat</span>
|
||||
</div>
|
||||
<div class="status-section-subtitle" id="gitSubtitle">Se încarcă...</div>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-section-toggle"></i>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Git:</span>
|
||||
<span class="status-value" id="gitStatus">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Raport:</span>
|
||||
<span class="status-value" id="lastReport">-</span>
|
||||
<span class="status-time" id="reportTime"></span>
|
||||
<div class="status-section-details" id="gitDetails">
|
||||
<!-- Populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="cron-row">
|
||||
<span class="cron-label">Cron azi:</span>
|
||||
<span class="cron-list" id="cronList">-</span>
|
||||
|
||||
<!-- ANAF Section -->
|
||||
<div class="status-section collapsed" id="anafSection">
|
||||
<div class="status-section-header" onclick="toggleStatusSection('anafSection')">
|
||||
<div class="status-section-icon anaf">
|
||||
<i data-lucide="building-2"></i>
|
||||
</div>
|
||||
<div class="status-section-info">
|
||||
<div class="status-section-title">
|
||||
ANAF Monitor
|
||||
<span class="status-badge ok" id="anafBadge">OK</span>
|
||||
</div>
|
||||
<div class="status-section-subtitle" id="anafSubtitle">Nicio modificare detectată</div>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-section-toggle"></i>
|
||||
</div>
|
||||
<div class="status-section-details" id="anafDetails">
|
||||
<div class="status-detail-item">
|
||||
<i data-lucide="clock"></i>
|
||||
<span id="anafLastCheck">Ultima verificare: -</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cron Section -->
|
||||
<div class="status-section" id="cronSection">
|
||||
<div class="status-section-header" onclick="toggleStatusSection('cronSection')">
|
||||
<div class="status-section-icon cron">
|
||||
<i data-lucide="clock"></i>
|
||||
</div>
|
||||
<div class="status-section-info">
|
||||
<div class="status-section-title">
|
||||
Cron Jobs
|
||||
<span class="status-badge ok" id="cronBadge">0/0</span>
|
||||
</div>
|
||||
<div class="status-section-subtitle" id="cronSubtitle">Jobs programate azi</div>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-section-toggle"></i>
|
||||
</div>
|
||||
<div class="status-section-details" id="cronDetails">
|
||||
<!-- Populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -911,64 +1087,205 @@
|
||||
|
||||
const priorityOrder = ['urgent-important', 'important', 'urgent', 'backlog'];
|
||||
|
||||
// Status
|
||||
// Status sections collapse state
|
||||
function getCollapsedStatusSections() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('collapsedStatusSections') || '["anafSection"]');
|
||||
} catch { return ['anafSection']; }
|
||||
}
|
||||
|
||||
function setCollapsedStatusSections(sections) {
|
||||
localStorage.setItem('collapsedStatusSections', JSON.stringify(sections));
|
||||
}
|
||||
|
||||
function initStatusSections() {
|
||||
const collapsed = getCollapsedStatusSections();
|
||||
collapsed.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.classList.add('collapsed');
|
||||
});
|
||||
}
|
||||
|
||||
function toggleStatusSection(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
|
||||
el.classList.toggle('collapsed');
|
||||
|
||||
const collapsed = getCollapsedStatusSections();
|
||||
const idx = collapsed.indexOf(id);
|
||||
if (el.classList.contains('collapsed')) {
|
||||
if (idx === -1) collapsed.push(id);
|
||||
} else {
|
||||
if (idx > -1) collapsed.splice(idx, 1);
|
||||
}
|
||||
setCollapsedStatusSections(collapsed);
|
||||
}
|
||||
|
||||
// Status loading
|
||||
async function loadStatus() {
|
||||
await Promise.all([
|
||||
loadGitStatus(),
|
||||
loadAnafStatus(),
|
||||
loadCronStatus()
|
||||
]);
|
||||
updateStatusSummary();
|
||||
}
|
||||
|
||||
async function refreshStatus() {
|
||||
showToast('Se reîmprospătează...');
|
||||
await loadStatus();
|
||||
showToast('Status actualizat!');
|
||||
}
|
||||
|
||||
async function loadGitStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/git?' + Date.now());
|
||||
if (!response.ok) throw new Error('API error');
|
||||
const git = await response.json();
|
||||
|
||||
// Update badge
|
||||
const badge = document.getElementById('gitBadge');
|
||||
if (git.clean) {
|
||||
badge.textContent = 'curat';
|
||||
badge.className = 'status-badge ok';
|
||||
} else {
|
||||
badge.textContent = git.uncommittedCount + ' modificări';
|
||||
badge.className = 'status-badge warning';
|
||||
}
|
||||
|
||||
// Update subtitle
|
||||
const subtitle = document.getElementById('gitSubtitle');
|
||||
subtitle.textContent = `${git.branch} · ${git.lastCommit.time}`;
|
||||
|
||||
// Update details
|
||||
const details = document.getElementById('gitDetails');
|
||||
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>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (git.uncommittedCount > 0) {
|
||||
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>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
details.innerHTML = html;
|
||||
lucide.createIcons();
|
||||
|
||||
return git;
|
||||
} catch (e) {
|
||||
console.error('Git status error:', e);
|
||||
document.getElementById('gitBadge').textContent = 'eroare';
|
||||
document.getElementById('gitBadge').className = 'status-badge error';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAnafStatus() {
|
||||
try {
|
||||
const response = await fetch('status.json?' + Date.now());
|
||||
if (response.ok) {
|
||||
const status = await response.json();
|
||||
updateStatus(status);
|
||||
if (!response.ok) throw new Error('No status.json');
|
||||
const status = await response.json();
|
||||
|
||||
if (status.anaf) {
|
||||
const badge = document.getElementById('anafBadge');
|
||||
badge.textContent = status.anaf.status || 'OK';
|
||||
badge.className = 'status-badge ' + (status.anaf.ok !== false ? 'ok' : 'warning');
|
||||
|
||||
const subtitle = document.getElementById('anafSubtitle');
|
||||
subtitle.textContent = status.anaf.message || 'Nicio modificare detectată';
|
||||
|
||||
if (status.anaf.lastCheck) {
|
||||
document.getElementById('anafLastCheck').textContent =
|
||||
'Ultima verificare: ' + status.anaf.lastCheck;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
} catch (e) {
|
||||
console.log('No status.json');
|
||||
console.log('No ANAF status');
|
||||
}
|
||||
updateCronList();
|
||||
}
|
||||
|
||||
function updateStatus(status) {
|
||||
const anafEl = document.getElementById('anafStatus');
|
||||
if (status.anaf) {
|
||||
anafEl.textContent = status.anaf.status;
|
||||
anafEl.className = 'status-value ' + (status.anaf.ok ? 'ok' : 'warning');
|
||||
}
|
||||
|
||||
const gitEl = document.getElementById('gitStatus');
|
||||
if (status.git) {
|
||||
gitEl.textContent = status.git.status;
|
||||
gitEl.className = 'status-value ' + (status.git.clean ? 'ok' : 'warning');
|
||||
}
|
||||
|
||||
const reportEl = document.getElementById('lastReport');
|
||||
const timeEl = document.getElementById('reportTime');
|
||||
if (status.lastReport) {
|
||||
reportEl.textContent = status.lastReport.summary || 'OK';
|
||||
timeEl.textContent = status.lastReport.time ? '(' + status.lastReport.time + ')' : '';
|
||||
}
|
||||
|
||||
const summaryEl = document.getElementById('statusSummary');
|
||||
let summary = [];
|
||||
if (status.anaf) summary.push('ANAF: ' + status.anaf.status);
|
||||
if (status.git) summary.push('Git: ' + status.git.status);
|
||||
summaryEl.textContent = summary.join(' · ') || 'OK';
|
||||
}
|
||||
|
||||
function updateCronList() {
|
||||
async function loadCronStatus() {
|
||||
const now = new Date();
|
||||
const hour = now.getHours();
|
||||
|
||||
// TODO: În viitor, fetch din /api/cron
|
||||
const jobs = [
|
||||
{ time: '07:30', name: 'coaching', done: hour >= 8 },
|
||||
{ time: '08:30', name: 'raport', done: hour >= 9 },
|
||||
{ time: '20:00', name: 'raport', done: hour >= 20 },
|
||||
{ time: '21:00', name: 'coaching', done: hour >= 21 }
|
||||
{ time: '06:30', name: 'morning-report', done: hour >= 7 },
|
||||
{ time: '07:00', name: 'morning-coaching', done: hour >= 7 },
|
||||
{ time: '18:00', name: 'evening-report', done: hour >= 18 },
|
||||
{ time: '19:00', name: 'evening-coaching', done: hour >= 19 }
|
||||
];
|
||||
|
||||
const listEl = document.getElementById('cronList');
|
||||
listEl.innerHTML = jobs.map(job =>
|
||||
`<span class="${job.done ? 'cron-done' : ''}">${job.time} ${job.name}</span>`
|
||||
).join(' · ');
|
||||
|
||||
const doneCount = jobs.filter(j => j.done).length;
|
||||
|
||||
// Update badge
|
||||
const badge = document.getElementById('cronBadge');
|
||||
badge.textContent = `${doneCount}/${jobs.length}`;
|
||||
badge.className = 'status-badge ok';
|
||||
|
||||
// Update subtitle
|
||||
const subtitle = document.getElementById('cronSubtitle');
|
||||
const nextJob = jobs.find(j => !j.done);
|
||||
subtitle.textContent = nextJob
|
||||
? `Următorul: ${nextJob.time} ${nextJob.name}`
|
||||
: 'Toate job-urile au rulat azi';
|
||||
|
||||
// Update details
|
||||
const details = document.getElementById('cronDetails');
|
||||
details.innerHTML = jobs.map(job => `
|
||||
<div class="cron-item ${job.done ? 'done' : 'pending'}">
|
||||
<i data-lucide="${job.done ? 'check-circle' : 'clock'}" class="cron-icon ${job.done ? 'done' : 'pending'}"></i>
|
||||
<span class="cron-time">${job.time}</span>
|
||||
<span class="cron-name">${job.name}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
function updateStatusSummary() {
|
||||
const gitBadge = document.getElementById('gitBadge');
|
||||
const anafBadge = document.getElementById('anafBadge');
|
||||
const cronBadge = document.getElementById('cronBadge');
|
||||
|
||||
const summary = document.getElementById('statusSummary');
|
||||
summary.textContent = `Git: ${gitBadge?.textContent || '-'} · ANAF: ${anafBadge?.textContent || '-'} · Cron: ${cronBadge?.textContent || '-'}`;
|
||||
}
|
||||
|
||||
// Auto-refresh on page focus
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
loadStatus();
|
||||
}
|
||||
});
|
||||
|
||||
// Load data
|
||||
async function loadIssues() {
|
||||
try {
|
||||
@@ -1323,6 +1640,7 @@
|
||||
});
|
||||
|
||||
// Init
|
||||
initStatusSections();
|
||||
loadStatus();
|
||||
loadIssues();
|
||||
loadActivity();
|
||||
|
||||
Reference in New Issue
Block a user