chore: auto-commit from dashboard
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ credentials/
|
||||
*.pid
|
||||
memory.bak/
|
||||
.use_openrouter
|
||||
.gstack/
|
||||
|
||||
@@ -155,6 +155,43 @@
|
||||
color: #818cf8;
|
||||
}
|
||||
|
||||
.status-section-icon.services {
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.status-section-icon.sessions {
|
||||
background: rgba(168, 85, 247, 0.15);
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
.status-section-icon.logs {
|
||||
background: rgba(156, 163, 175, 0.15);
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.status-section-icon.doctor {
|
||||
background: rgba(20, 184, 166, 0.15);
|
||||
color: #14b8a6;
|
||||
}
|
||||
|
||||
.status-section-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.status-section-actions .btn {
|
||||
font-size: var(--text-xs);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
}
|
||||
|
||||
/* Heavy content (services grid, sessions list, logs, doctor) — full width, no left indent */
|
||||
.status-section-details.full-width {
|
||||
padding: 0 var(--space-4) var(--space-3);
|
||||
}
|
||||
|
||||
.status-section-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@@ -904,44 +941,9 @@
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────
|
||||
Eco panels (merged from eco.html)
|
||||
Eco panels (merged from eco.html — now nested under Status)
|
||||
───────────────────────────────────────────────────────── */
|
||||
|
||||
/* Collapsible section (used by Services/Git/Sessions/Logs/Doctor) */
|
||||
.section {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-3);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.section.collapsed .section-body { display: none; }
|
||||
.section.collapsed .section-header { margin-bottom: 0; }
|
||||
.section.collapsed .sec-chev { transform: rotate(180deg); }
|
||||
|
||||
.sec-chev {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-muted);
|
||||
transition: transform var(--transition-fast);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* Service cards */
|
||||
.services-grid {
|
||||
display: grid;
|
||||
@@ -1309,100 +1311,6 @@
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
/* Git panel (top-level) */
|
||||
.git-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.git-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.git-header:hover { background: var(--bg-elevated); }
|
||||
|
||||
.git-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
background: rgba(249, 115, 22, 0.15);
|
||||
color: #f97316;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.git-icon svg { width: 18px; height: 18px; }
|
||||
|
||||
.git-info { flex: 1; min-width: 0; }
|
||||
|
||||
.git-title {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.git-subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.git-badge {
|
||||
padding: 3px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.git-badge.ok { background: rgba(34, 197, 94, 0.15); color: #22c55e; }
|
||||
.git-badge.warning { background: rgba(249, 115, 22, 0.15); color: #f97316; }
|
||||
.git-badge.error { background: rgba(239, 68, 68, 0.15); color: #ef4444; }
|
||||
|
||||
.git-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.git-toggle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-muted);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.git-card.collapsed .git-toggle { transform: rotate(-90deg); }
|
||||
.git-card.collapsed .git-details { display: none; }
|
||||
|
||||
.git-details {
|
||||
padding: 0 var(--space-4) var(--space-3);
|
||||
padding-left: calc(var(--space-4) + 32px + var(--space-3));
|
||||
}
|
||||
|
||||
.git-detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.git-detail-item svg { width: 14px; height: 14px; color: var(--text-secondary); }
|
||||
.git-detail-item.uncommitted { color: #f97316; }
|
||||
|
||||
/* Spinner */
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
@@ -1536,6 +1444,99 @@
|
||||
<i data-lucide="chevron-down" class="status-toggle"></i>
|
||||
</div>
|
||||
<div class="status-content">
|
||||
<!-- 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>
|
||||
<div class="status-section-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-secondary" onclick="ecoGitCommit()" title="Commit & Push" aria-label="Commit și push">
|
||||
<i data-lucide="git-commit"></i> Commit
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="loadGitStatus()" title="Refresh" aria-label="Refresh git status">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-section-toggle"></i>
|
||||
</div>
|
||||
<div class="status-section-details" id="gitDetails">
|
||||
<div class="skeleton-row" style="width:70%"></div>
|
||||
<div class="skeleton-row" style="width:40%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Services Section -->
|
||||
<div class="status-section collapsed" id="servicesSection">
|
||||
<div class="status-section-header" onclick="toggleStatusSection('servicesSection')">
|
||||
<div class="status-section-icon services">
|
||||
<i data-lucide="server"></i>
|
||||
</div>
|
||||
<div class="status-section-info">
|
||||
<div class="status-section-title">
|
||||
Services
|
||||
<span class="status-badge ok" id="servicesBadge">—</span>
|
||||
</div>
|
||||
<div class="status-section-subtitle" id="servicesSubtitle">Se încarcă...</div>
|
||||
</div>
|
||||
<div class="status-section-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-secondary" onclick="loadServices()" title="Refresh services" aria-label="Refresh services">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-section-toggle"></i>
|
||||
</div>
|
||||
<div class="status-section-details full-width">
|
||||
<div class="services-grid" id="servicesGrid">
|
||||
<div class="service-card"><div class="skeleton-row" style="width:60%"></div><div class="skeleton-row" style="width:80%"></div><div class="skeleton-row" style="width:40%"></div></div>
|
||||
<div class="service-card"><div class="skeleton-row" style="width:70%"></div><div class="skeleton-row" style="width:50%"></div><div class="skeleton-row" style="width:60%"></div></div>
|
||||
<div class="service-card"><div class="skeleton-row" style="width:50%"></div><div class="skeleton-row" style="width:70%"></div><div class="skeleton-row" style="width:55%"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sessions Section -->
|
||||
<div class="status-section collapsed" id="sessionsSection">
|
||||
<div class="status-section-header" onclick="toggleStatusSection('sessionsSection')">
|
||||
<div class="status-section-icon sessions">
|
||||
<i data-lucide="message-square"></i>
|
||||
</div>
|
||||
<div class="status-section-info">
|
||||
<div class="status-section-title">
|
||||
Sessions
|
||||
<span class="status-badge ok" id="sessionsBadge">—</span>
|
||||
</div>
|
||||
<div class="status-section-subtitle" id="sessionsSubtitle">Canale active</div>
|
||||
</div>
|
||||
<div class="status-section-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-secondary" onclick="loadSessions()" title="Refresh sessions" aria-label="Refresh sessions">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-danger" onclick="clearAllSessions()" id="clearAllBtn" aria-label="Clear all sessions" style="display:none;">
|
||||
<i data-lucide="trash-2"></i> Clear All
|
||||
</button>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-section-toggle"></i>
|
||||
</div>
|
||||
<div class="status-section-details full-width">
|
||||
<div class="sessions-card">
|
||||
<div id="sessionsContent">
|
||||
<div style="padding:var(--space-3)">
|
||||
<div class="skeleton-row" style="width:70%"></div>
|
||||
<div class="skeleton-row" style="width:50%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ANAF Section -->
|
||||
<div class="status-section collapsed" id="anafSection">
|
||||
<div class="status-section-header" onclick="toggleStatusSection('anafSection')">
|
||||
@@ -1579,69 +1580,70 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Git Panel (promoted from Status Bar sub-section to top-level) -->
|
||||
<div class="section" id="sec-git">
|
||||
<div class="section-header" onclick="toggleSec('sec-git')">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="git-branch" style="width:18px;height:18px;"></i>
|
||||
Git
|
||||
</h2>
|
||||
<svg class="s-chevron sec-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="git-card" id="gitCard">
|
||||
<div class="git-header" onclick="toggleGitDetails()">
|
||||
<div class="git-icon" aria-hidden="true">
|
||||
<i data-lucide="git-branch"></i>
|
||||
<!-- Logs Section (lazy-loaded) -->
|
||||
<div class="status-section collapsed" id="logsSection" data-loaded="0">
|
||||
<div class="status-section-header" onclick="toggleStatusSection('logsSection')">
|
||||
<div class="status-section-icon logs">
|
||||
<i data-lucide="scroll-text"></i>
|
||||
</div>
|
||||
<div class="git-info">
|
||||
<div class="git-title">
|
||||
Git
|
||||
<span class="git-badge ok" id="gitBadge">curat</span>
|
||||
<div class="status-section-info">
|
||||
<div class="status-section-title">Logs</div>
|
||||
<div class="status-section-subtitle">echo-core.service journalctl</div>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-section-toggle"></i>
|
||||
</div>
|
||||
<div class="status-section-details full-width">
|
||||
<div class="log-card">
|
||||
<div class="log-toolbar">
|
||||
<label for="logLines">Lines:</label>
|
||||
<select id="logLines" onchange="loadLogs()">
|
||||
<option value="50">50</option>
|
||||
<option value="100" selected>100</option>
|
||||
<option value="200">200</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" onclick="loadLogs()" aria-label="Refresh logs" style="font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
Refresh
|
||||
</button>
|
||||
<div style="margin-left:auto;display:flex;align-items:center;gap:var(--space-2);">
|
||||
<label for="autoRefreshLogs" style="font-size:var(--text-xs);color:var(--text-muted);">Auto</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="autoRefreshLogs" onchange="toggleAutoRefreshLogs()">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="git-subtitle" id="gitSubtitle">Se încarcă...</div>
|
||||
<div class="log-content" id="logContent">Apasă Refresh sau expandă secțiunea pentru a încărca.</div>
|
||||
</div>
|
||||
<div class="git-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-secondary" onclick="ecoGitCommit()" title="Commit & Push" aria-label="Commit și push" style="font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="git-commit"></i> Commit
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="loadGitStatus()" title="Refresh" aria-label="Refresh git status" style="font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="git-toggle" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="git-details" id="gitDetails">
|
||||
<div class="skeleton-row" style="width:70%"></div>
|
||||
<div class="skeleton-row" style="width:40%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Services Panel -->
|
||||
<div class="section" id="sec-services">
|
||||
<div class="section-header" onclick="toggleSec('sec-services')">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="server" style="width:18px;height:18px;"></i>
|
||||
Services
|
||||
</h2>
|
||||
<div style="display:flex;gap:var(--space-2);align-items:center;">
|
||||
<button class="btn btn-secondary" onclick="event.stopPropagation();loadServices()" title="Refresh services" aria-label="Refresh services" style="font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
<svg class="s-chevron sec-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="services-grid" id="servicesGrid">
|
||||
<div class="service-card"><div class="skeleton-row" style="width:60%"></div><div class="skeleton-row" style="width:80%"></div><div class="skeleton-row" style="width:40%"></div></div>
|
||||
<div class="service-card"><div class="skeleton-row" style="width:70%"></div><div class="skeleton-row" style="width:50%"></div><div class="skeleton-row" style="width:60%"></div></div>
|
||||
<div class="service-card"><div class="skeleton-row" style="width:50%"></div><div class="skeleton-row" style="width:70%"></div><div class="skeleton-row" style="width:55%"></div></div>
|
||||
<!-- Doctor Section -->
|
||||
<div class="status-section collapsed" id="doctorSection">
|
||||
<div class="status-section-header" onclick="toggleStatusSection('doctorSection')">
|
||||
<div class="status-section-icon doctor">
|
||||
<i data-lucide="stethoscope"></i>
|
||||
</div>
|
||||
<div class="status-section-info">
|
||||
<div class="status-section-title">Doctor</div>
|
||||
<div class="status-section-subtitle">Diagnosticare sistem</div>
|
||||
</div>
|
||||
<div class="status-section-actions" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-primary" onclick="runDoctor()" id="doctorBtn" aria-label="Run diagnostics">
|
||||
<i data-lucide="play"></i> Run
|
||||
</button>
|
||||
</div>
|
||||
<i data-lucide="chevron-down" class="status-section-toggle"></i>
|
||||
</div>
|
||||
<div class="status-section-details full-width">
|
||||
<div class="doctor-card" id="doctorCard">
|
||||
<ul class="check-list" id="doctorChecks">
|
||||
<li class="check-item" style="color:var(--text-muted);justify-content:center;">Apasă pentru a rula verificările.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1701,93 +1703,6 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sessions Panel -->
|
||||
<div class="section" id="sec-sessions">
|
||||
<div class="section-header" onclick="toggleSec('sec-sessions')">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="message-square" style="width:18px;height:18px;"></i>
|
||||
Sessions
|
||||
</h2>
|
||||
<div style="display:flex;gap:var(--space-2);align-items:center;">
|
||||
<button class="btn btn-secondary" onclick="event.stopPropagation();loadSessions()" title="Refresh sessions" aria-label="Refresh sessions" style="font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-danger" onclick="event.stopPropagation();clearAllSessions()" id="clearAllBtn" aria-label="Clear all sessions" style="display:none;font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="trash-2"></i> Clear All
|
||||
</button>
|
||||
<svg class="s-chevron sec-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="sessions-card">
|
||||
<div id="sessionsContent">
|
||||
<div style="padding:var(--space-3)">
|
||||
<div class="skeleton-row" style="width:70%"></div>
|
||||
<div class="skeleton-row" style="width:50%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs Panel (lazy-loaded) -->
|
||||
<div class="section collapsed" id="sec-logs" data-loaded="0">
|
||||
<div class="section-header" onclick="toggleSec('sec-logs')">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="scroll-text" style="width:18px;height:18px;"></i>
|
||||
Logs
|
||||
</h2>
|
||||
<svg class="s-chevron sec-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="log-card">
|
||||
<div class="log-toolbar">
|
||||
<label for="logLines">Lines:</label>
|
||||
<select id="logLines" onchange="loadLogs()">
|
||||
<option value="50">50</option>
|
||||
<option value="100" selected>100</option>
|
||||
<option value="200">200</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" onclick="loadLogs()" aria-label="Refresh logs" style="font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
Refresh
|
||||
</button>
|
||||
<div style="margin-left:auto;display:flex;align-items:center;gap:var(--space-2);">
|
||||
<label for="autoRefreshLogs" style="font-size:var(--text-xs);color:var(--text-muted);">Auto</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="autoRefreshLogs" onchange="toggleAutoRefreshLogs()">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-content" id="logContent">Apasă Refresh sau expandă secțiunea pentru a încărca.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Doctor Panel (lazy-loaded) -->
|
||||
<div class="section collapsed" id="sec-doctor">
|
||||
<div class="section-header" onclick="toggleSec('sec-doctor')">
|
||||
<h2 class="section-title">
|
||||
<i data-lucide="stethoscope" style="width:18px;height:18px;"></i>
|
||||
Doctor
|
||||
</h2>
|
||||
<div style="display:flex;gap:var(--space-2);align-items:center;">
|
||||
<button class="btn btn-primary" onclick="event.stopPropagation();runDoctor()" id="doctorBtn" aria-label="Run diagnostics" style="font-size:var(--text-xs);padding:var(--space-1) var(--space-2);">
|
||||
<i data-lucide="play"></i> Run
|
||||
</button>
|
||||
<svg class="s-chevron sec-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<div class="doctor-card" id="doctorCard">
|
||||
<ul class="check-list" id="doctorChecks">
|
||||
<li class="check-item" style="color:var(--text-muted);justify-content:center;">Apasă pentru a rula verificările.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Note Overlay -->
|
||||
@@ -1999,9 +1914,10 @@
|
||||
|
||||
// Status sections collapse state
|
||||
function getCollapsedStatusSections() {
|
||||
const DEFAULTS = ['servicesSection', 'sessionsSection', 'anafSection', 'logsSection', 'doctorSection'];
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('collapsedStatusSections') || '["anafSection"]');
|
||||
} catch { return ['anafSection']; }
|
||||
return JSON.parse(localStorage.getItem('collapsedStatusSections') || JSON.stringify(DEFAULTS));
|
||||
} catch { return DEFAULTS; }
|
||||
}
|
||||
|
||||
function setCollapsedStatusSections(sections) {
|
||||
@@ -2009,19 +1925,29 @@
|
||||
}
|
||||
|
||||
function initStatusSections() {
|
||||
const collapsed = getCollapsedStatusSections();
|
||||
collapsed.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.classList.add('collapsed');
|
||||
const collapsed = new Set(getCollapsedStatusSections());
|
||||
document.querySelectorAll('#statusBar .status-section').forEach(el => {
|
||||
if (collapsed.has(el.id)) {
|
||||
el.classList.add('collapsed');
|
||||
} else {
|
||||
el.classList.remove('collapsed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleStatusSection(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
|
||||
|
||||
const wasCollapsed = el.classList.contains('collapsed');
|
||||
el.classList.toggle('collapsed');
|
||||
|
||||
|
||||
// Lazy-load logs the first time the section opens.
|
||||
if (id === 'logsSection' && wasCollapsed && el.dataset.loaded !== '1') {
|
||||
el.dataset.loaded = '1';
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
const collapsed = getCollapsedStatusSections();
|
||||
const idx = collapsed.indexOf(id);
|
||||
if (el.classList.contains('collapsed')) {
|
||||
@@ -2092,17 +2018,17 @@
|
||||
|
||||
if (git.clean) {
|
||||
badge.textContent = 'curat';
|
||||
badge.className = 'git-badge ok';
|
||||
badge.className = 'status-badge ok';
|
||||
} else {
|
||||
badge.textContent = git.uncommittedCount + ' modificări';
|
||||
badge.className = 'git-badge warning';
|
||||
badge.className = 'status-badge warning';
|
||||
}
|
||||
|
||||
subtitle.textContent = `${git.branch} · ${git.lastCommit.time}`;
|
||||
|
||||
const GITEA_URL = 'https://gitea.romfast.ro/romfast/echo-core';
|
||||
let html = `
|
||||
<div class="git-detail-item">
|
||||
<div class="status-detail-item">
|
||||
<i data-lucide="git-commit"></i>
|
||||
<span><a href="${GITEA_URL}/commit/${git.lastCommit.hash}" target="_blank" style="color:var(--accent)">${git.lastCommit.hash}</a> ${git.lastCommit.message.substring(0, 50)}${git.lastCommit.message.length > 50 ? '...' : ''} (${git.lastCommit.time})</span>
|
||||
</div>
|
||||
@@ -2110,15 +2036,15 @@
|
||||
if (git.uncommittedCount > 0) {
|
||||
const files = (git.uncommittedParsed || []).slice(0, 5).map(f => f.path).join(', ');
|
||||
const more = git.uncommittedCount > 5 ? ` +${git.uncommittedCount - 5}` : '';
|
||||
html += `<div class="git-detail-item uncommitted">
|
||||
html += `<div class="status-detail-item uncommitted">
|
||||
<i data-lucide="alert-circle"></i>
|
||||
<span><a href="/echo/files.html?git=1" style="color:var(--warning)"><strong>${git.uncommittedCount}</strong> necomise</a>: ${files}${more}</span>
|
||||
</div>`;
|
||||
}
|
||||
if (git.diffStat) {
|
||||
html += `<div class="git-detail-item" style="font-family:var(--font-mono);white-space:pre-wrap;"><i data-lucide="bar-chart-3"></i><span>${escapeHtml(git.diffStat)}</span></div>`;
|
||||
html += `<div class="status-detail-item" style="font-family:var(--font-mono);white-space:pre-wrap;"><i data-lucide="bar-chart-3"></i><span>${escapeHtml(git.diffStat)}</span></div>`;
|
||||
}
|
||||
html += `<div class="git-detail-item">
|
||||
html += `<div class="status-detail-item">
|
||||
<i data-lucide="external-link"></i>
|
||||
<a href="${GITEA_URL}" target="_blank" style="color:var(--accent)">gitea.romfast.ro/romfast/echo-core</a>
|
||||
</div>`;
|
||||
@@ -2129,7 +2055,7 @@
|
||||
} catch (e) {
|
||||
console.error('Git status error:', e);
|
||||
badge.textContent = 'eroare';
|
||||
badge.className = 'git-badge error';
|
||||
badge.className = 'status-badge error';
|
||||
subtitle.textContent = 'nu se poate încărca status-ul git';
|
||||
details.innerHTML = `<div class="panel-error" role="alert"><i data-lucide="alert-triangle"></i><span>${escapeHtml(e.message || 'eroare necunoscută')}</span><button onclick="loadGitStatus()">Retry</button></div>`;
|
||||
lucide.createIcons();
|
||||
@@ -2882,22 +2808,6 @@
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function toggleSec(id) {
|
||||
const sec = document.getElementById(id);
|
||||
if (!sec) return;
|
||||
const wasCollapsed = sec.classList.contains('collapsed');
|
||||
sec.classList.toggle('collapsed');
|
||||
// Lazy-load logs when expanded for the first time.
|
||||
if (id === 'sec-logs' && wasCollapsed && sec.dataset.loaded !== '1') {
|
||||
sec.dataset.loaded = '1';
|
||||
loadLogs();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleGitDetails() {
|
||||
document.getElementById('gitCard').classList.toggle('collapsed');
|
||||
}
|
||||
|
||||
function formatUptime(seconds) {
|
||||
if (!seconds && seconds !== 0) return '-';
|
||||
const d = Math.floor(seconds / 86400);
|
||||
@@ -2934,6 +2844,21 @@
|
||||
|
||||
function renderServices(services) {
|
||||
const grid = document.getElementById('servicesGrid');
|
||||
|
||||
// Update the Status sub-section header (badge + subtitle).
|
||||
const badge = document.getElementById('servicesBadge');
|
||||
const subtitle = document.getElementById('servicesSubtitle');
|
||||
if (badge && subtitle) {
|
||||
const total = services.length;
|
||||
const active = services.filter(s => s.active).length;
|
||||
badge.textContent = `${active}/${total}`;
|
||||
badge.className = active === total ? 'status-badge ok' : 'status-badge warning';
|
||||
const stopped = services.filter(s => !s.active).map(s => svcLabel(s.name));
|
||||
subtitle.textContent = stopped.length === 0
|
||||
? 'Toate servicile rulează'
|
||||
: `Oprite: ${stopped.join(', ')}`;
|
||||
}
|
||||
|
||||
grid.innerHTML = services.map(svc => {
|
||||
const isTaskboard = svc.name === 'echo-taskboard';
|
||||
const canControl = !isTaskboard;
|
||||
@@ -3099,6 +3024,21 @@
|
||||
const container = document.getElementById('sessionsContent');
|
||||
const clearBtn = document.getElementById('clearAllBtn');
|
||||
|
||||
// Update the Status sub-section header (badge + subtitle).
|
||||
const badge = document.getElementById('sessionsBadge');
|
||||
const subtitle = document.getElementById('sessionsSubtitle');
|
||||
if (badge && subtitle) {
|
||||
const count = sessions ? sessions.length : 0;
|
||||
badge.textContent = String(count);
|
||||
badge.className = 'status-badge ok';
|
||||
if (count === 0) {
|
||||
subtitle.textContent = 'Nicio sesiune activă';
|
||||
} else {
|
||||
const platforms = [...new Set(sessions.map(s => s.platform || 'unknown'))];
|
||||
subtitle.textContent = platforms.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
if (!sessions || sessions.length === 0) {
|
||||
container.innerHTML = '<div class="empty-state-warm">Nicio sesiune activă. Un DM către Echo pornește una.</div>';
|
||||
clearBtn.style.display = 'none';
|
||||
@@ -3218,7 +3158,12 @@
|
||||
// ── Doctor ──────────────────────────────────────────────
|
||||
|
||||
async function runDoctor() {
|
||||
document.getElementById('sec-doctor').classList.remove('collapsed');
|
||||
// Expand both the parent Status bar and the Doctor sub-section so the user sees results.
|
||||
document.getElementById('statusBar').classList.remove('collapsed');
|
||||
const doctorSection = document.getElementById('doctorSection');
|
||||
if (doctorSection.classList.contains('collapsed')) {
|
||||
toggleStatusSection('doctorSection');
|
||||
}
|
||||
const list = document.getElementById('doctorChecks');
|
||||
const btn = document.getElementById('doctorBtn');
|
||||
|
||||
|
||||
@@ -53,6 +53,21 @@
|
||||
"video": "",
|
||||
"tldr": "| Yann Tiersen | Comptine d'un autre été (Extended 1h) | https://www.youtube.com/watch?v=nJQV1jCM0gk |"
|
||||
},
|
||||
{
|
||||
"file": "notes-data/youtube/2026-04-25_claude-code-playwright-automates-anything.md",
|
||||
"title": "Claude Code + Playwright Automates Literally Anything",
|
||||
"date": "2026-04-25",
|
||||
"tags": [],
|
||||
"domains": [
|
||||
"work"
|
||||
],
|
||||
"types": [],
|
||||
"category": "youtube",
|
||||
"project": null,
|
||||
"subdir": null,
|
||||
"video": "",
|
||||
"tldr": "Claude Code + Playwright CLI = automatizezi orice în browser, inclusiv în conturi unde ești logat. Se scrie un script Playwright, se testează, agentul învață din erori și îmbunătățește scriptul iterat..."
|
||||
},
|
||||
{
|
||||
"file": "notes-data/tools/claude-agent-projects.md",
|
||||
"title": "Proiecte pe LXC 171 — claude-agent",
|
||||
@@ -8505,8 +8520,8 @@
|
||||
"title": "Proiect: Vending Master - Integrare Website → ROA",
|
||||
"date": "2026-01-30",
|
||||
"tags": [
|
||||
"integrare",
|
||||
"vending-master"
|
||||
"vending-master",
|
||||
"integrare"
|
||||
],
|
||||
"domains": [
|
||||
"work"
|
||||
@@ -8986,9 +9001,9 @@
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"total": 516,
|
||||
"total": 517,
|
||||
"by_domain": {
|
||||
"work": 162,
|
||||
"work": 163,
|
||||
"health": 97,
|
||||
"growth": 233,
|
||||
"sprijin": 39,
|
||||
@@ -9006,7 +9021,7 @@
|
||||
"reflectii": 3,
|
||||
"retete": 1,
|
||||
"tools": 7,
|
||||
"youtube": 104,
|
||||
"youtube": 105,
|
||||
"memory": 43
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Claude Code + Playwright Automates Literally Anything
|
||||
|
||||
**Sursa:** https://www.youtube.com/watch?v=J-6pnl5DQg8
|
||||
**Data:** 2026-04-25
|
||||
**Durata:** 18:50
|
||||
**Tags:** @work @automation @tools
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
Claude Code + Playwright CLI = automatizezi orice în browser, inclusiv în conturi unde ești logat. Se scrie un script Playwright, se testează, agentul învață din erori și îmbunătățește scriptul iterativ. Use case-urile demo: QA automat pentru web app, scraping contacte (telefoane dentisti), și interacțiuni în platforme cu login (School). Se pot programa ca sarcini recurente headless.
|
||||
|
||||
---
|
||||
|
||||
## Puncte Cheie
|
||||
|
||||
1. **Playwright CLI vs MCP** — CLI consumă mult mai puțini tokeni decât MCP-ul Chrome DevTools (important pentru proiecte mari). Preferabil CLI.
|
||||
|
||||
2. **Flow de bază:**
|
||||
- Instalezi Playwright CLI în proiect
|
||||
- Claude Code scrie un script JS pentru automatizare
|
||||
- Rulezi script → găsești buguri → Claude îl îmbunătățește → repeat
|
||||
- Fiecare rulare face scriptul mai bun (agent învață din erori)
|
||||
|
||||
3. **QA automat:**
|
||||
- Poți cere lui Claude să construiască un app și apoi să-l testeze singur
|
||||
- Găsește buguri (ex: textarea cu Enter nu avansa), le fixează, rerulează
|
||||
- Se poate transforma într-un **skill** → QA consistent și repetabil
|
||||
|
||||
4. **Sesiuni autentificate:**
|
||||
- Opțiunea 1: Persistent browser profile (folosește datele Chrome existente)
|
||||
- Prima rulare: login manual → sesiunea se salvează → rulările ulterioare = logged in automat
|
||||
- Funcționează pe platforme "anti-automation" (ex: School/Skool)
|
||||
|
||||
5. **Skills recurente (autonome):**
|
||||
- Se pot programa ca scheduled tasks pe desktop app
|
||||
- Agentul AIS rulează automat: daily news roundup, likes pe wins, răspunsuri la notificări
|
||||
- Headless = rulează fără să se vadă tab-ul
|
||||
- Iterativ: când dă de un task nou (ex: vot pe poll) → creează un script nou → actualizează skill-ul
|
||||
|
||||
6. **Iterarea e normală:**
|
||||
- Nu te aștepți să funcționeze din prima
|
||||
- Course-correct cu instrucțiuni clare (ex: "thumbs up galben = deja likat, sari-l")
|
||||
- 4-5 iterații → rezultat stabil
|
||||
|
||||
---
|
||||
|
||||
## Citate Notabile
|
||||
|
||||
> "When you give a tool as powerful as Claude Code access to literally control a browser, you can actually automate anything."
|
||||
|
||||
> "Every time that you use the script, it's going to get better."
|
||||
|
||||
> "The fact that you could automate QA by having multiple different headed or headless browsers spin up and running and fixing is a complete game changer."
|
||||
|
||||
---
|
||||
|
||||
## Idei & Aplicabilitate (Echo/Marius)
|
||||
|
||||
- **roa2web QA:** Claude Code + Playwright ar putea testa automat interfața web ROA — detectează buguri înainte să ajungă la clienți
|
||||
- **ANAF Monitor:** Dacă pagina ANAF se schimbă UI și hash-ul nu mai e suficient, Playwright poate extrage conținut vizual
|
||||
- **Automatizare rapoarte:** Descărcare automată de rapoarte din portale fără API (ex: bancare, fiscale)
|
||||
- **Skill recurent:** Orice flux repetitiv în browser (login + download + procesare) se poate transforma în scheduled task
|
||||
|
||||
---
|
||||
|
||||
## Resurse Menționate
|
||||
|
||||
- Playwright CLI (alternativa la MCP Chrome DevTools)
|
||||
- Alte CLI-uri: Forcell Agent Browser, Open CLI
|
||||
- Video următor (din canal): Scheduled Tasks cu Claude Code
|
||||
Reference in New Issue
Block a user