Some checks failed
Build and Deploy Gateway / build-and-deploy (push) Failing after 13s
92 lines
3.4 KiB
JavaScript
92 lines
3.4 KiB
JavaScript
import React from 'react';
|
|
import { formatBytes, formatRelative } from '../utils/format.js';
|
|
|
|
function StatusRow({ label, value, kind }) {
|
|
return (
|
|
<div className="row">
|
|
<span className="key">{label}</span>
|
|
<span className={`val ${kind ? 'text-' + kind : ''}`}>{value}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function StatusPane({ state, busy, onStop, onRestart, onShowConfig }) {
|
|
const userInfo = state?.userInfo;
|
|
const traffic = userInfo
|
|
? `${formatBytes((userInfo.upload || 0) + (userInfo.download || 0))} / ${userInfo.total ? formatBytes(userInfo.total) : '∞'}`
|
|
: '—';
|
|
|
|
let singboxStatus = 'Остановлен';
|
|
let singboxKind = 'muted';
|
|
if (state?.singboxRunning) {
|
|
singboxStatus = `работает · ${formatRelative(state.singboxStartedAt)}`;
|
|
singboxKind = 'success';
|
|
} else if (state?.configExists) {
|
|
singboxStatus = 'остановлен (конфиг есть)';
|
|
singboxKind = 'warning';
|
|
}
|
|
|
|
return (
|
|
<aside className="status-pane">
|
|
<div className="card compact flat">
|
|
<div className="card-header no-margin">
|
|
<h3>sing-box</h3>
|
|
<span className={`badge ${state?.singboxRunning ? 'success' : 'neutral'}`}>
|
|
{state?.singboxRunning ? '● online' : '○ offline'}
|
|
</span>
|
|
</div>
|
|
<div className="kv-list" style={{ marginTop: 12 }}>
|
|
<StatusRow label="Статус" value={singboxStatus} kind={singboxKind} />
|
|
<StatusRow label="UI порт" value={`:${state?.port || 3456}`} />
|
|
<StatusRow label="Mixed proxy" value={`${state?.proxyBindIp || '0.0.0.0'}:${state?.proxyPort || 8080}`} />
|
|
<StatusRow label="TProxy" value={`:${state?.tproxyPort || 7895}`} />
|
|
<StatusRow label="RU direct" value={state?.routingRuDirect ? 'включено' : 'выключено'} />
|
|
<StatusRow label="Трафик" value={traffic} />
|
|
<StatusRow
|
|
label="Применено"
|
|
value={state?.appliedAt ? formatRelative(state.appliedAt) : 'не применено'}
|
|
/>
|
|
</div>
|
|
|
|
<div className="btn-group" style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
<button
|
|
className="btn btn-secondary sm block"
|
|
disabled={busy || !state?.configExists}
|
|
onClick={onRestart}
|
|
>
|
|
↻ Перезапустить
|
|
</button>
|
|
<button
|
|
className="btn btn-ghost sm block"
|
|
disabled={busy || !state?.singboxRunning}
|
|
onClick={onStop}
|
|
>
|
|
■ Остановить
|
|
</button>
|
|
<button
|
|
className="btn btn-ghost sm block"
|
|
disabled={!state?.configExists}
|
|
onClick={onShowConfig}
|
|
>
|
|
⌘ Показать config
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{state?.appliedHistory?.length > 0 && (
|
|
<div className="card compact flat">
|
|
<h4 style={{ marginBottom: 8 }}>История применений</h4>
|
|
<div className="events-list">
|
|
{state.appliedHistory.slice(0, 5).map((h) => (
|
|
<div key={h.at} className="event-row" style={{ gridTemplateColumns: '1fr auto' }}>
|
|
<span className="text-truncate">{h.tag}</span>
|
|
<span className="event-time">{formatRelative(h.at)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</aside>
|
|
);
|
|
}
|