All checks were successful
Beta Release / beta (push) Successful in 36s
Add full internationalization system with React context, French/English translations, and AZERTY/QWERTY keyboard layout support. Dashboard now uses a tabbed layout (Tools, Notifications, Workflows). Config page exposes language and keyboard preferences persisted via new /api/preferences endpoint. 💕 Generated with Crush Assisted-by: GLM-5-Turbo via Crush <crush@charm.land>
111 lines
4.0 KiB
JavaScript
111 lines
4.0 KiB
JavaScript
import { useState } from 'react'
|
|
import { useI18n } from '../i18n'
|
|
|
|
export default function Dashboard({ tools, updates, api, onRescan }) {
|
|
const { t, layout } = useI18n()
|
|
const [activeSection, setActiveSection] = useState('tools')
|
|
const [notifications, setNotifications] = useState([])
|
|
|
|
const installed = tools.filter(tool => tool.installed).length
|
|
const total = tools.length
|
|
|
|
const addNotif = (text, type) => {
|
|
setNotifications(prev => [{ text, type, id: Date.now(), time: new Date() }, ...prev])
|
|
}
|
|
|
|
const sections = [
|
|
{ id: 'tools', label: t('dashboard.systemOverview') },
|
|
{ id: 'notifications', label: t('dashboard.activityLog') },
|
|
{ id: 'workflows', label: t('studio.workflows') },
|
|
]
|
|
|
|
return (
|
|
<div className="dashboard-layout">
|
|
<div className="dashboard-tabs">
|
|
{sections.map(s => (
|
|
<div
|
|
key={s.id}
|
|
className={`dashboard-tab ${activeSection === s.id ? 'active' : ''}`}
|
|
onClick={() => setActiveSection(s.id)}
|
|
>
|
|
{s.label}
|
|
{s.id === 'tools' && total > 0 && (
|
|
<span className="tab-count">{installed}/{total}</span>
|
|
)}
|
|
{s.id === 'notifications' && notifications.length > 0 && (
|
|
<span className="tab-count warn">{notifications.length}</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="dashboard-content">
|
|
{activeSection === 'tools' && (
|
|
<div className="dashboard-tools">
|
|
{tools.length === 0 ? (
|
|
<div className="empty-state">{t('dashboard.noUpdateData')}</div>
|
|
) : (
|
|
<div className="tools-compact">
|
|
{tools.map((tool, i) => {
|
|
const name = tool.name || tool.Name
|
|
const ver = extractVersion(tool.Version || tool.version)
|
|
return (
|
|
<div key={i} className="tool-compact-row">
|
|
<span className={`badge sm ${tool.installed ? 'ok' : 'error'}`}>
|
|
{tool.installed ? '\u2713' : '\u2717'}
|
|
</span>
|
|
<span className="tool-compact-name">{name}</span>
|
|
{ver && <span className="tool-compact-ver">{ver}</span>}
|
|
{tool.installed && <span className="tool-compact-installed">{t('dashboard.installed')}</span>}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{activeSection === 'notifications' && (
|
|
<div className="dashboard-notifications">
|
|
{notifications.length === 0 ? (
|
|
<div className="empty-state">{t('dashboard.noUpdateData')}</div>
|
|
) : (
|
|
notifications.map(n => (
|
|
<div key={n.id} className={`notif-row notif-${n.type}`}>
|
|
<span className="notif-time">
|
|
{n.time.toLocaleTimeString(layout.locale, { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
|
|
</span>
|
|
<span className="notif-text">{n.text}</span>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{activeSection === 'workflows' && (
|
|
<div className="dashboard-workflows">
|
|
<div className="workflow-section">
|
|
<div className="section-label">{t('studio.workflows')}</div>
|
|
<div className="empty-state" style={{ padding: 20 }}>
|
|
{t('studio.noWorkflow')}
|
|
</div>
|
|
</div>
|
|
<div className="workflow-section">
|
|
<div className="section-label">{t('studio.activeAgents')}</div>
|
|
<div className="empty-state" style={{ padding: 20 }}>
|
|
{t('studio.noWorkflow')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function extractVersion(s) {
|
|
if (!s) return ''
|
|
const m = s.match(/\d+\.\d+\.\d+/)
|
|
return m ? m[0] : s.slice(0, 12)
|
|
}
|