Update ACP system navigation
This commit is contained in:
@@ -96,6 +96,7 @@ function Acp({ isAdmin }) {
|
||||
const [systemStatus, setSystemStatus] = useState(null)
|
||||
const [systemLoading, setSystemLoading] = useState(false)
|
||||
const [systemError, setSystemError] = useState('')
|
||||
const [systemSection, setSystemSection] = useState('info')
|
||||
const [usersPage, setUsersPage] = useState(1)
|
||||
const [usersPerPage, setUsersPerPage] = useState(10)
|
||||
const [userSort, setUserSort] = useState({ columnId: 'name', direction: 'asc' })
|
||||
@@ -3507,254 +3508,352 @@ function Acp({ isAdmin }) {
|
||||
<Tab eventKey="system" title={t('acp.system')}>
|
||||
{systemError && <p className="text-danger">{systemError}</p>}
|
||||
{systemLoading && <p className="bb-muted">{t('acp.loading')}</p>}
|
||||
{!systemLoading && systemStatus && (
|
||||
<div className="bb-acp-panel">
|
||||
<div className="bb-acp-panel-header">
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
<h5 className="mb-0">{t('system.requirements')}</h5>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('acp.refresh')}
|
||||
</Button>
|
||||
{!systemLoading && (
|
||||
<Row className="g-4">
|
||||
<Col lg={3} xl={2}>
|
||||
<div className="bb-acp-sidebar">
|
||||
<div className="bb-acp-sidebar-section">
|
||||
<div className="bb-acp-sidebar-title">{t('acp.system')}</div>
|
||||
<div className="list-group">
|
||||
<button
|
||||
type="button"
|
||||
className={`list-group-item list-group-item-action ${
|
||||
systemSection === 'info' ? 'is-active' : ''
|
||||
}`}
|
||||
onClick={() => setSystemSection('info')}
|
||||
>
|
||||
Overview
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`list-group-item list-group-item-action ${
|
||||
systemSection === 'insite' ? 'is-active' : ''
|
||||
}`}
|
||||
onClick={() => setSystemSection('insite')}
|
||||
>
|
||||
Live Update
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`list-group-item list-group-item-action ${
|
||||
systemSection === 'cli' ? 'is-active' : ''
|
||||
}`}
|
||||
onClick={() => setSystemSection('cli')}
|
||||
>
|
||||
CLI
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`list-group-item list-group-item-action ${
|
||||
systemSection === 'ci' ? 'is-active' : ''
|
||||
}`}
|
||||
onClick={() => setSystemSection('ci')}
|
||||
>
|
||||
CI/CD
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bb-acp-panel-body">
|
||||
<table className="bb-acp-stats-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('system.check')}</th>
|
||||
<th>{t('system.path')}</th>
|
||||
<th>{t('system.min_version')}</th>
|
||||
<th>{t('system.current_version')}</th>
|
||||
<th>{t('system.status')}</th>
|
||||
<th>{t('system.recheck')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>PHP</td>
|
||||
<td className="bb-acp-stats-value">{systemStatus.php_selected_path || '—'}</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.min_versions?.php || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.php_selected_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon
|
||||
status={systemStatus.php_selected_ok ? 'ok' : 'bad'}
|
||||
/>
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
</Col>
|
||||
<Col lg={9} xl={10}>
|
||||
{systemSection === 'info' && (
|
||||
<div className="bb-acp-panel">
|
||||
<div className="bb-acp-panel-header">
|
||||
<h5 className="mb-0">System overview</h5>
|
||||
</div>
|
||||
<div className="bb-acp-panel-body">
|
||||
<p className="bb-muted mb-0">
|
||||
Placeholder: summary, upgrade guidance, and environment health notes will
|
||||
live here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{systemSection === 'insite' && (
|
||||
<div className="bb-acp-panel">
|
||||
<div className="bb-acp-panel-header">
|
||||
<h5 className="mb-0">Live Update</h5>
|
||||
</div>
|
||||
<div className="bb-acp-panel-body">
|
||||
<p className="bb-muted mb-0">
|
||||
Placeholder: run a live update from inside the forum, with safety checks
|
||||
and status details.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{systemSection === 'cli' && (
|
||||
<div className="bb-acp-panel">
|
||||
<div className="bb-acp-panel-header">
|
||||
<h5 className="mb-0">CLI</h5>
|
||||
</div>
|
||||
<div className="bb-acp-panel-body">
|
||||
<p className="bb-muted mb-0">
|
||||
Placeholder: CLI upgrade commands and automation helpers will live here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{systemSection === 'ci' && (
|
||||
<div className="bb-acp-panel">
|
||||
<div className="bb-acp-panel-header">
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
<h5 className="mb-0">{t('system.requirements')}</h5>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
{t('acp.refresh')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Composer</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.composer || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.min_versions?.composer || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.composer_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.composer ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Node</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.node || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.min_versions?.node || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.node_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.node ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>npm</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.npm || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.min_versions?.npm || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.npm_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.npm ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tar</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.tar || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.tar_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.tar ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>rsync</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.rsync || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.rsync_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.rsync ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>proc_* functions</td>
|
||||
<td className="bb-acp-stats-value" colSpan={3}>
|
||||
{systemStatus.proc_functions
|
||||
? Object.entries(systemStatus.proc_functions)
|
||||
.filter(([, ok]) => !ok)
|
||||
.map(([name]) => name)
|
||||
.join(', ')
|
||||
: '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon
|
||||
status={
|
||||
Boolean(systemStatus.proc_functions) &&
|
||||
Object.values(systemStatus.proc_functions).every(Boolean)
|
||||
? 'ok'
|
||||
: 'bad'
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t('system.storage_writable')}</td>
|
||||
<td className="bb-acp-stats-value">storage/</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.storage_writable ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t('system.updates_writable')}</td>
|
||||
<td className="bb-acp-stats-value">storage/app/updates</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.updates_writable ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bb-acp-panel-body">
|
||||
{!systemStatus && (
|
||||
<p className="bb-muted mb-0">
|
||||
{t('system.not_found')}
|
||||
</p>
|
||||
)}
|
||||
{systemStatus && (
|
||||
<table className="bb-acp-stats-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('system.check')}</th>
|
||||
<th>{t('system.path')}</th>
|
||||
<th>{t('system.min_version')}</th>
|
||||
<th>{t('system.current_version')}</th>
|
||||
<th>{t('system.status')}</th>
|
||||
<th>{t('system.recheck')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>PHP</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.php_selected_path || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.min_versions?.php || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.php_selected_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon
|
||||
status={systemStatus.php_selected_ok ? 'ok' : 'bad'}
|
||||
/>
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Composer</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.composer || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.min_versions?.composer || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.composer_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.composer ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Node</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.node || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.min_versions?.node || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.node_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.node ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>npm</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.npm || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.min_versions?.npm || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.npm_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.npm ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tar</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.tar || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.tar_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.tar ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>rsync</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.rsync || t('system.not_found')}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
{systemStatus.rsync_version || '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.rsync ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>proc_* functions</td>
|
||||
<td className="bb-acp-stats-value" colSpan={3}>
|
||||
{systemStatus.proc_functions
|
||||
? Object.entries(systemStatus.proc_functions)
|
||||
.filter(([, ok]) => !ok)
|
||||
.map(([name]) => name)
|
||||
.join(', ')
|
||||
: '—'}
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon
|
||||
status={
|
||||
Boolean(systemStatus.proc_functions) &&
|
||||
Object.values(systemStatus.proc_functions).every(Boolean)
|
||||
? 'ok'
|
||||
: 'bad'
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t('system.storage_writable')}</td>
|
||||
<td className="bb-acp-stats-value">storage/</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.storage_writable ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t('system.updates_writable')}</td>
|
||||
<td className="bb-acp-stats-value">storage/app/updates</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">—</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<StatusIcon status={systemStatus.updates_writable ? 'ok' : 'bad'} />
|
||||
</td>
|
||||
<td className="bb-acp-stats-value">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="dark"
|
||||
onClick={loadSystemStatus}
|
||||
disabled={systemLoading}
|
||||
>
|
||||
{t('system.recheck')}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Reference in New Issue
Block a user