Refine ACP system settings
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-12
|
||||||
|
- Refined ACP System tab with left navigation, section-specific requirements, and CLI PHP selector.
|
||||||
|
- Added CLI PHP interpreter options (php, keyhelp-php-domain, custom) with KeyHelp guidance.
|
||||||
|
- Updated CLI update tooling and automation notes (KeyHelp PHP handling, CI runner requirements).
|
||||||
|
|
||||||
## 2026-02-10
|
## 2026-02-10
|
||||||
- Reshaped ACP System tab with left navigation and dedicated views (Overview, Live Update, CLI, CI/CD).
|
- Reshaped ACP System tab with left navigation and dedicated views (Overview, Live Update, CLI, CI/CD).
|
||||||
- Moved system requirements table into the CI/CD view with refresh controls.
|
- Moved system requirements table into the CI/CD view with refresh controls.
|
||||||
|
|||||||
4
NOTES.md
4
NOTES.md
@@ -9,5 +9,7 @@ Progress (last 2 days):
|
|||||||
- Added coverage scripts and cleanup (tests for update/version flows, system update/status, attachments, forums, roles, ranks, settings, portal, etc.).
|
- Added coverage scripts and cleanup (tests for update/version flows, system update/status, attachments, forums, roles, ranks, settings, portal, etc.).
|
||||||
- Hardened tests with fakes/mocks to cover error paths and edge cases.
|
- Hardened tests with fakes/mocks to cover error paths and edge cases.
|
||||||
|
|
||||||
TODO: Make PHP binary path configurable for updates if default PHP is outdated (ACP -> System).
|
TODO: Make the PHP binary path configurable for updates if the default PHP is outdated (ACP -> System).
|
||||||
CI/CD: Runner must have PHP 8.4+ as the default CLI interpreter.
|
CI/CD: Runner must have PHP 8.4+ as the default CLI interpreter.
|
||||||
|
KeyHelp: `keyhelp-php-domain` can select the PHP version based on the domain of the script location.
|
||||||
|
KeyHelp: `keyhelp-php-domain` is a Pro feature; on non-Pro setups we must fake the command.
|
||||||
|
|||||||
@@ -98,5 +98,5 @@
|
|||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"version": "26.0.2",
|
"version": "26.0.2",
|
||||||
"build": "59"
|
"build": "60"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,7 +202,8 @@ function Acp({ isAdmin }) {
|
|||||||
favicon_256: '',
|
favicon_256: '',
|
||||||
})
|
})
|
||||||
const [systemCliSettings, setSystemCliSettings] = useState({
|
const [systemCliSettings, setSystemCliSettings] = useState({
|
||||||
php_binary: '',
|
php_mode: 'php',
|
||||||
|
php_custom: '',
|
||||||
})
|
})
|
||||||
const [systemCliSaving, setSystemCliSaving] = useState(false)
|
const [systemCliSaving, setSystemCliSaving] = useState(false)
|
||||||
const [systemCliError, setSystemCliError] = useState('')
|
const [systemCliError, setSystemCliError] = useState('')
|
||||||
@@ -297,8 +298,15 @@ function Acp({ isAdmin }) {
|
|||||||
favicon_256: settingsMap.get('favicon_256') || '',
|
favicon_256: settingsMap.get('favicon_256') || '',
|
||||||
}
|
}
|
||||||
setGeneralSettings(next)
|
setGeneralSettings(next)
|
||||||
|
const configuredPhp = settingsMap.get('system.php_binary') || ''
|
||||||
|
const phpMode = configuredPhp === 'keyhelp-php-domain'
|
||||||
|
? 'keyhelp'
|
||||||
|
: configuredPhp === '' || configuredPhp === 'php'
|
||||||
|
? 'php'
|
||||||
|
: 'custom'
|
||||||
setSystemCliSettings({
|
setSystemCliSettings({
|
||||||
php_binary: settingsMap.get('system.php_binary') || '',
|
php_mode: phpMode,
|
||||||
|
php_custom: phpMode === 'custom' ? configuredPhp : '',
|
||||||
})
|
})
|
||||||
setAttachmentSettings({
|
setAttachmentSettings({
|
||||||
display_images_inline: settingsMap.get('attachments.display_images_inline') || 'true',
|
display_images_inline: settingsMap.get('attachments.display_images_inline') || 'true',
|
||||||
@@ -386,11 +394,22 @@ function Acp({ isAdmin }) {
|
|||||||
setSystemCliSaving(true)
|
setSystemCliSaving(true)
|
||||||
setSystemCliError('')
|
setSystemCliError('')
|
||||||
try {
|
try {
|
||||||
const value = typeof systemCliSettings.php_binary === 'string'
|
let value = ''
|
||||||
? systemCliSettings.php_binary.trim()
|
if (systemCliSettings.php_mode === 'custom') {
|
||||||
: String(systemCliSettings.php_binary ?? '')
|
value = typeof systemCliSettings.php_custom === 'string'
|
||||||
|
? systemCliSettings.php_custom.trim()
|
||||||
|
: String(systemCliSettings.php_custom ?? '')
|
||||||
|
} else if (systemCliSettings.php_mode === 'keyhelp') {
|
||||||
|
value = 'keyhelp-php-domain'
|
||||||
|
} else {
|
||||||
|
value = 'php'
|
||||||
|
}
|
||||||
await saveSetting('system.php_binary', value)
|
await saveSetting('system.php_binary', value)
|
||||||
setSystemCliSettings((prev) => ({ ...prev, php_binary: value }))
|
setSystemCliSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
php_mode: systemCliSettings.php_mode,
|
||||||
|
php_custom: systemCliSettings.php_mode === 'custom' ? value : '',
|
||||||
|
}))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setSystemCliError(err.message)
|
setSystemCliError(err.message)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -398,6 +417,107 @@ function Acp({ isAdmin }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const systemChecks = useMemo(() => {
|
||||||
|
if (!systemStatus) return []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'php',
|
||||||
|
label: 'PHP',
|
||||||
|
path: systemStatus.php_selected_path || '—',
|
||||||
|
min: systemStatus.min_versions?.php || '—',
|
||||||
|
current: systemStatus.php_selected_version || '—',
|
||||||
|
status: systemStatus.php_selected_ok ? 'ok' : 'bad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'composer',
|
||||||
|
label: 'Composer',
|
||||||
|
path: systemStatus.composer || t('system.not_found'),
|
||||||
|
min: systemStatus.min_versions?.composer || '—',
|
||||||
|
current: systemStatus.composer_version || '—',
|
||||||
|
status: systemStatus.composer ? 'ok' : 'bad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'node',
|
||||||
|
label: 'Node',
|
||||||
|
path: systemStatus.node || t('system.not_found'),
|
||||||
|
min: systemStatus.min_versions?.node || '—',
|
||||||
|
current: systemStatus.node_version || '—',
|
||||||
|
status: systemStatus.node ? 'ok' : 'bad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'npm',
|
||||||
|
label: 'npm',
|
||||||
|
path: systemStatus.npm || t('system.not_found'),
|
||||||
|
min: systemStatus.min_versions?.npm || '—',
|
||||||
|
current: systemStatus.npm_version || '—',
|
||||||
|
status: systemStatus.npm ? 'ok' : 'bad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tar',
|
||||||
|
label: 'tar',
|
||||||
|
path: systemStatus.tar || t('system.not_found'),
|
||||||
|
min: '—',
|
||||||
|
current: systemStatus.tar_version || '—',
|
||||||
|
status: systemStatus.tar ? 'ok' : 'bad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rsync',
|
||||||
|
label: 'rsync',
|
||||||
|
path: systemStatus.rsync || t('system.not_found'),
|
||||||
|
min: '—',
|
||||||
|
current: systemStatus.rsync_version || '—',
|
||||||
|
status: systemStatus.rsync ? 'ok' : 'bad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'proc',
|
||||||
|
label: 'proc_* functions',
|
||||||
|
path: systemStatus.proc_functions
|
||||||
|
? Object.entries(systemStatus.proc_functions)
|
||||||
|
.filter(([, ok]) => !ok)
|
||||||
|
.map(([name]) => name)
|
||||||
|
.join(', ')
|
||||||
|
: '—',
|
||||||
|
min: '—',
|
||||||
|
current: '—',
|
||||||
|
note: 'Optional. Needed for automated version checks.',
|
||||||
|
status:
|
||||||
|
Boolean(systemStatus.proc_functions) &&
|
||||||
|
Object.values(systemStatus.proc_functions).every(Boolean)
|
||||||
|
? 'ok'
|
||||||
|
: 'bad',
|
||||||
|
pathColSpan: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'storage',
|
||||||
|
label: t('system.storage_writable'),
|
||||||
|
path: 'storage/',
|
||||||
|
min: '—',
|
||||||
|
current: '—',
|
||||||
|
status: systemStatus.storage_writable ? 'ok' : 'bad',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'updates',
|
||||||
|
label: t('system.updates_writable'),
|
||||||
|
path: 'storage/app/updates',
|
||||||
|
min: '—',
|
||||||
|
current: '—',
|
||||||
|
status: systemStatus.updates_writable ? 'ok' : 'bad',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}, [systemStatus, t])
|
||||||
|
|
||||||
|
const visibleSystemChecks = useMemo(() => {
|
||||||
|
const visibilityBySection = {
|
||||||
|
insite: ['php', 'proc', 'storage', 'updates'],
|
||||||
|
cli: ['php', 'composer', 'node', 'npm', 'proc', 'storage', 'updates'],
|
||||||
|
ci: ['php', 'composer', 'node', 'npm', 'tar', 'rsync', 'proc', 'storage', 'updates'],
|
||||||
|
info: [],
|
||||||
|
}
|
||||||
|
const allowed = new Set(visibilityBySection[systemSection] || [])
|
||||||
|
return systemChecks.filter((check) => allowed.has(check.id))
|
||||||
|
}, [systemChecks, systemSection])
|
||||||
|
|
||||||
|
|
||||||
const handleLogoUpload = async (file, settingKey) => {
|
const handleLogoUpload = async (file, settingKey) => {
|
||||||
if (!file) return
|
if (!file) return
|
||||||
setGeneralUploading(true)
|
setGeneralUploading(true)
|
||||||
@@ -848,6 +968,74 @@ function Acp({ isAdmin }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSystemRequirementsPanel() {
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
{visibleSystemChecks.map((check) => (
|
||||||
|
<tr key={check.id}>
|
||||||
|
<td>{check.label}</td>
|
||||||
|
<td className="bb-acp-stats-value text-start" colSpan={check.pathColSpan || 1}>
|
||||||
|
{check.path}
|
||||||
|
{check.note && <div className="bb-muted mt-1 text-center">{check.note}</div>}
|
||||||
|
</td>
|
||||||
|
{!check.pathColSpan && (
|
||||||
|
<>
|
||||||
|
<td className="bb-acp-stats-value">{check.min}</td>
|
||||||
|
<td className="bb-acp-stats-value">{check.current}</td>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<td className="bb-acp-stats-value">
|
||||||
|
<StatusIcon status={check.status} />
|
||||||
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
handleVersionCheck()
|
handleVersionCheck()
|
||||||
@@ -3595,6 +3783,7 @@ function Acp({ isAdmin }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{systemSection === 'insite' && (
|
{systemSection === 'insite' && (
|
||||||
|
<>
|
||||||
<div className="bb-acp-panel">
|
<div className="bb-acp-panel">
|
||||||
<div className="bb-acp-panel-header">
|
<div className="bb-acp-panel-header">
|
||||||
<h5 className="mb-0">Live Update</h5>
|
<h5 className="mb-0">Live Update</h5>
|
||||||
@@ -3606,6 +3795,8 @@ function Acp({ isAdmin }) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{renderSystemRequirementsPanel()}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{systemSection === 'cli' && (
|
{systemSection === 'cli' && (
|
||||||
<div className="bb-acp-panel">
|
<div className="bb-acp-panel">
|
||||||
@@ -3617,20 +3808,36 @@ function Acp({ isAdmin }) {
|
|||||||
<Form onSubmit={handleSystemCliSave}>
|
<Form onSubmit={handleSystemCliSave}>
|
||||||
<Form.Group className="mb-3">
|
<Form.Group className="mb-3">
|
||||||
<Form.Label>PHP interpreter</Form.Label>
|
<Form.Label>PHP interpreter</Form.Label>
|
||||||
<Form.Control
|
<Form.Select
|
||||||
type="text"
|
className="mb-2"
|
||||||
placeholder={systemStatus?.php_default || '/usr/bin/php'}
|
value={systemCliSettings.php_mode}
|
||||||
value={systemCliSettings.php_binary}
|
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setSystemCliSettings((prev) => ({
|
setSystemCliSettings((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
php_binary: event.target.value,
|
php_mode: event.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="php">php (system default)</option>
|
||||||
|
<option value="keyhelp">keyhelp-php-domain</option>
|
||||||
|
<option value="custom">Custom binary (e.g. keyhelp-php84)</option>
|
||||||
|
</Form.Select>
|
||||||
|
{systemCliSettings.php_mode === 'custom' && (
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="keyhelp-php84"
|
||||||
|
value={systemCliSettings.php_custom}
|
||||||
|
onChange={(event) =>
|
||||||
|
setSystemCliSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
php_custom: event.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<Form.Text className="bb-muted">
|
<Form.Text className="bb-muted">
|
||||||
Used for CLI-based updates and maintenance tasks. Leave empty to use
|
Used for CLI-based updates and maintenance tasks. `keyhelp-php-domain`
|
||||||
the system default.
|
is available on KeyHelp Pro; on non-Pro setups use a custom binary.
|
||||||
</Form.Text>
|
</Form.Text>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Button type="submit" variant="dark" disabled={systemCliSaving}>
|
<Button type="submit" variant="dark" disabled={systemCliSaving}>
|
||||||
@@ -3640,263 +3847,22 @@ function Acp({ isAdmin }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{systemSection === 'cli' && renderSystemRequirementsPanel()}
|
||||||
{systemSection === 'ci' && (
|
{systemSection === 'ci' && (
|
||||||
|
<>
|
||||||
<div className="bb-acp-panel">
|
<div className="bb-acp-panel">
|
||||||
<div className="bb-acp-panel-header">
|
<div className="bb-acp-panel-header">
|
||||||
<div className="d-flex align-items-center justify-content-between">
|
<h5 className="mb-0">CI/CD</h5>
|
||||||
<h5 className="mb-0">{t('system.requirements')}</h5>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
size="sm"
|
|
||||||
variant="dark"
|
|
||||||
onClick={loadSystemStatus}
|
|
||||||
disabled={systemLoading}
|
|
||||||
>
|
|
||||||
{t('acp.refresh')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="bb-acp-panel-body">
|
<div className="bb-acp-panel-body">
|
||||||
{!systemStatus && (
|
|
||||||
<p className="bb-muted mb-0">
|
<p className="bb-muted mb-0">
|
||||||
{t('system.not_found')}
|
Placeholder: CI/CD pipelines, runner requirements, and deployment logs will
|
||||||
|
live here.
|
||||||
</p>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
{renderSystemRequirementsPanel()}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
Reference in New Issue
Block a user