feat(route-optimizer): improve table layout and enhance data display with optimized column widths and text wrapping
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m51s

This commit is contained in:
2026-03-05 10:35:04 +07:00
parent 79828eace9
commit b792cede50
+29 -13
View File
@@ -22,6 +22,19 @@ function fmtProb(v) {
return typeof v === 'number' ? `${v}%` : '—';
}
const OPT_TABLE_STYLE = { tableLayout: 'fixed', width: '100%' };
const OPT_COLS = ['24%', '20%', '22%', '18%', '16%'];
function OptimizerColgroup() {
return (
<colgroup>
{OPT_COLS.map((w) => (
<col key={w} style={{ width: w }} />
))}
</colgroup>
);
}
export default function RouteOptimizerPage() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
@@ -131,7 +144,8 @@ export default function RouteOptimizerPage() {
)}
</div>
<div className="table-responsive">
<table className="table table-vcenter card-table">
<table className="table table-vcenter card-table align-middle" style={OPT_TABLE_STYLE}>
<OptimizerColgroup />
<thead>
<tr>
<th>Jumphost</th>
@@ -150,9 +164,9 @@ export default function RouteOptimizerPage() {
</tr>
) : hjCandidates.map((c) => (
<tr key={c.id}>
<td className="fw-semibold">{c.jumphost?.label || c.jumphost?.dns || c.jumphost?.ip || 'jumphost'}</td>
<td className="small text-muted">{c.interfaceName || '—'}</td>
<td className="small text-muted">
<td className="fw-semibold text-break">{c.jumphost?.label || c.jumphost?.dns || c.jumphost?.ip || 'jumphost'}</td>
<td className="small text-muted text-break">{c.interfaceName || '—'}</td>
<td className="small text-muted text-break">
ping: {c.pingMs ?? '—'} ms, speed: {c.speedMbps ?? '—'} Mbps
</td>
<td className="small">
@@ -187,7 +201,8 @@ export default function RouteOptimizerPage() {
<div className="fw-semibold mb-2">Полные маршруты Home Jumphost Exit</div>
<div className="table-responsive">
<table className="table table-vcenter card-table">
<table className="table table-vcenter card-table align-middle" style={OPT_TABLE_STYLE}>
<OptimizerColgroup />
<thead>
<tr>
<th>Маршрут</th>
@@ -206,7 +221,7 @@ export default function RouteOptimizerPage() {
</tr>
) : routes.map((r) => (
<tr key={r.id}>
<td>
<td className="text-break">
<span className="fw-semibold">{r.home?.label}</span>
<span className="text-muted"> </span>
<span className="fw-semibold">{r.jumphost?.label}</span>
@@ -216,10 +231,10 @@ export default function RouteOptimizerPage() {
<td>
<span className="badge bg-green-lt text-green">{r.probabilityOptimal}%</span>
</td>
<td className="small text-muted">
<td className="small text-muted text-break">
ping: {r.homeToJumphost?.pingMs ?? '—'} ms, speed: {r.homeToJumphost?.speedMbps ?? '—'} Mbps
</td>
<td className="small text-muted">
<td className="small text-muted text-break">
ping: {r.jumphostToExit?.pingMs ?? '—'} ms, speed: {r.jumphostToExit?.speedMbps ?? '—'} Mbps
</td>
<td className="small">{r.score}</td>
@@ -261,7 +276,8 @@ export default function RouteOptimizerPage() {
</div>
<div className="card-body">
<div className="table-responsive">
<table className="table table-vcenter card-table">
<table className="table table-vcenter card-table align-middle" style={OPT_TABLE_STYLE}>
<OptimizerColgroup />
<thead>
<tr>
<th>Community</th>
@@ -280,7 +296,7 @@ export default function RouteOptimizerPage() {
</tr>
) : rows.map((r) => (
<tr key={`${jh.label}-${r.community}`}>
<td>
<td className="text-break">
<div className="fw-semibold">{r.community}</div>
{(r.communityInfo?.name || r.communityInfo?.description) && (
<div className="small text-muted">
@@ -288,7 +304,7 @@ export default function RouteOptimizerPage() {
</div>
)}
</td>
<td className="small">
<td className="small text-break">
<div>{r.current?.gateway || r.currentGateway || '—'}</div>
{r.current?.tunnelGateway && (
<div className="text-muted">
@@ -303,7 +319,7 @@ export default function RouteOptimizerPage() {
</div>
)}
</td>
<td className="small">
<td className="small text-break">
<div>{r.recommended?.gateway || '—'}</div>
{r.pinnedBySettings && (
<div className="text-muted">
@@ -326,7 +342,7 @@ export default function RouteOptimizerPage() {
<td className="small">
{fmtProb(r.current?.probabilityOptimal)} / {fmtProb(r.recommended?.probabilityOptimal)}
</td>
<td>
<td className="text-break">
{r.current == null && r.recommended == null ? (
<span className="badge bg-secondary-lt text-secondary">Недостаточно данных</span>
) : r.pinnedBySettings && !r.pinnedGatewayResolved ? (