refactor(web): redesign frontend for native web UX
All checks were successful
Beta Release / beta (push) Successful in 33s

- Replace all TUI artifacts: [OK], [FAIL], >>, [■], [$] with proper
  web components (badges, cards, chips, avatars)
- Rename CSS variables from TUI names (cyberRed, dimRed, bgVoid)
  to semantic names (accent, accent-dim, bg)
- Add proper interactive elements: hover states, cursor pointer,
  click feedback (scale), focus rings, spinner animation
- Fix user-select: was none globally, now allows text selection
- Redesign navigation: proper tabs with role="tab" and aria attributes
- Add keyboard shortcuts only when not in input/textarea (1-4 for tabs)
- Replace footer TUI shortcuts with clean statusbar
- Dashboard: card-based layout, badge status, progress bar, activity log
- Studio: message bubbles (aligned left/right), agent cards with avatars
- Shell: command history (ArrowUp/Down), toggleable AI panel button,
  panel header with current directory
- Config: provider cards, color swatches for theme picker,
  clean field rows with empty states
- CSS imported via main.jsx (not HTML link) for proper Vite hashing
- Remove glitch/scanline/typewriter TUI animations
- Add favicon

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
Augustin
2026-04-21 21:25:55 +02:00
parent aa0ff199c6
commit 3dc24ae22c
9 changed files with 620 additions and 829 deletions

View File

@@ -1,115 +1,129 @@
import { useState, useEffect } from 'react'
import { useState } from 'react'
export default function Dashboard({ tools, updates, api, onRescan }) {
const [installing, setInstalling] = useState(false)
const [installLog, setInstallLog] = useState([])
const [log, setLog] = useState([])
const installed = tools.filter(t => t.installed).length
const total = tools.length
const pct = total > 0 ? (installed / total) * 100 : 0
const missing = tools.filter(t => !t.installed).map(t => t.Name || t.name)
const pct = total > 0 ? Math.round((installed / total) * 100) : 0
const missing = tools.filter(t => !t.installed).map(t => t.name || t.Name)
const handleInstall = async () => {
if (missing.length === 0) return
setInstalling(true)
setInstallLog(prev => [...prev, { text: `Installing ${missing.length} tools...`, type: 'info' }])
addLog(`Installing ${missing.length} tools...`, 'info')
try {
await api.installTools(missing)
setInstallLog(prev => [...prev, { text: 'Install started. Rescan to see changes.', type: 'ok' }])
const data = await api.runScan()
const toolData = await api.getTools()
onRescan(toolData.tools || [])
addLog('Install started. Rescanning...', 'ok')
await api.runScan()
const data = await api.getTools()
onRescan(data.tools || [])
addLog('Done.', 'ok')
} catch (err) {
setInstallLog(prev => [...prev, { text: err.message, type: 'error' }])
addLog(err.message, 'error')
}
setInstalling(false)
}
const handleScan = async () => {
addLog('Scanning system...', 'info')
await api.runScan()
const data = await api.getTools()
onRescan(data.tools || [])
addLog('Scan complete.', 'ok')
}
const handleCheckUpdates = async () => {
const data = await api.getUpdates().catch(() => ({ updates: [] }))
const count = (data.updates || []).filter(u => u.needsUpdate).length
addLog(count > 0 ? `${count} updates available.` : 'All tools up to date.', count > 0 ? 'warn' : 'ok')
}
const addLog = (text, type) => setLog(prev => [...prev, { text, type, id: Date.now() }])
return (
<div className="grid-2">
<div style={{ overflow: 'auto', padding: '4px' }}>
<div className="section-header">System</div>
<div style={{ marginBottom: 16 }}>
<span style={{ color: 'var(--text-main)' }}>{installed}/{total} tools installed</span>
</div>
<div className="section-header">Installed Tools</div>
<div style={{ marginBottom: 8 }}>
{tools.map((t, i) => (
<div key={i} className="tool-item">
<span className={`tool-status ${t.installed ? 'ok' : 'missing'}`}>
{t.installed ? '[OK]' : '[--]'}
</span>
<span className="tool-name">{t.Name || t.name}</span>
{(t.Version || t.version) && (
<span className="tool-version">{extractVersion(t.Version || t.version)}</span>
)}
</div>
))}
</div>
<div className="progress-bar" style={{ marginBottom: 16 }}>
<div className="progress-fill" style={{ width: `${pct}%` }} />
</div>
{installing && (
<div style={{ marginBottom: 16 }}>
<span className="loading-spinner"> Installing...</span>
<div style={{ overflow: 'auto' }}>
<div className="card">
<div className="card-header">
System Overview &mdash; {installed}/{total} tools ({pct}%)
</div>
<div className="progress" style={{ marginBottom: 16 }}>
<div className="progress-fill" style={{ width: `${pct}%` }} />
</div>
)}
{installLog.length > 0 && (
<div>
<div className="section-header">Install Log</div>
{installLog.map((log, i) => (
<div key={i} style={{
color: log.type === 'error' ? 'var(--error)' :
log.type === 'ok' ? 'var(--success)' : 'var(--text-dim)',
fontSize: 12, padding: '2px 0'
{tools.map((t, i) => {
const name = t.name || t.Name
const ver = extractVersion(t.Version || t.version)
return (
<div key={i} className="tool-row">
<span className={`badge ${t.installed ? 'ok' : 'error'}`}>
{t.installed ? 'Installed' : 'Missing'}
</span>
<span className="tool-name">{name}</span>
{ver && <span className="tool-version">{ver}</span>}
</div>
)
})}
</div>
</div>
</div>
<div style={{ overflow: 'auto', display: 'flex', flexDirection: 'column', gap: 20 }}>
<div className="card">
<div className="card-header">Quick Actions</div>
<div className="actions-stack">
<button onClick={handleInstall} disabled={installing || missing.length === 0}>
{installing && <span className="spinner" />}
Install missing ({missing.length})
</button>
<button onClick={handleCheckUpdates}>Check for updates</button>
<button onClick={handleScan}>Rescan system</button>
<button onClick={() => api.configureMCP().then(() => addLog('MCP configured.', 'ok'))}>
Configure MCP
</button>
</div>
</div>
<div className="card" style={{ flex: 1 }}>
<div className="card-header">Updates</div>
{updates.length === 0 ? (
<div className="empty-state">No update data yet.</div>
) : (
updates.map((u, i) => (
<div key={i} className="tool-row">
<span className={`badge ${u.needsUpdate ? 'warn' : 'ok'}`}>
{u.needsUpdate ? 'Update' : 'Latest'}
</span>
<span className="tool-name">{u.tool}</span>
{u.needsUpdate && (
<span style={{ color: 'var(--warning)', fontSize: 12, fontFamily: 'var(--font-mono)' }}>
{u.current} &rarr; {u.latest}
</span>
)}
</div>
))
)}
</div>
{log.length > 0 && (
<div className="card">
<div className="card-header">Activity Log</div>
{log.map(entry => (
<div key={entry.id} style={{
fontSize: 12,
padding: '4px 0',
color: entry.type === 'error' ? 'var(--error)' :
entry.type === 'warn' ? 'var(--warning)' :
entry.type === 'ok' ? 'var(--success)' : 'var(--text-tertiary)',
}}>
{log.text}
{entry.text}
</div>
))}
</div>
)}
</div>
<div style={{ overflow: 'auto', padding: '4px' }}>
<div className="section-header">Quick Actions</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 16 }}>
<button onClick={handleInstall} disabled={installing || missing.length === 0}>
[i] Install missing ({missing.length})
</button>
<button onClick={() => api.getUpdates().then(d => {})}> [u] Check updates</button>
<button onClick={handleScan}>[s] Rescan system</button>
<button onClick={() => api.configureMCP()}>[m] Configure MCP</button>
</div>
<div className="section-header">Updates</div>
<div style={{ marginBottom: 16 }}>
{updates.length === 0 ? (
<span style={{ color: 'var(--text-muted)' }}>No update data yet</span>
) : updates.map((u, i) => (
<div key={i} className="tool-item">
<span className={`tool-status ${u.needsUpdate ? 'missing' : 'ok'}`}>
{u.needsUpdate ? '[!!]' : '[OK]'}
</span>
<span className="tool-name">{u.tool}</span>
{u.needsUpdate && (
<span style={{ color: 'var(--warning)', fontSize: 11 }}>
{u.current} {u.latest}
</span>
)}
</div>
))}
</div>
</div>
</div>
)
}