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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m51s
This commit is contained in:
@@ -22,6 +22,19 @@ function fmtProb(v) {
|
|||||||
return typeof v === 'number' ? `${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() {
|
export default function RouteOptimizerPage() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
@@ -131,7 +144,8 @@ export default function RouteOptimizerPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="table-responsive">
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Jumphost</th>
|
<th>Jumphost</th>
|
||||||
@@ -150,9 +164,9 @@ export default function RouteOptimizerPage() {
|
|||||||
</tr>
|
</tr>
|
||||||
) : hjCandidates.map((c) => (
|
) : hjCandidates.map((c) => (
|
||||||
<tr key={c.id}>
|
<tr key={c.id}>
|
||||||
<td className="fw-semibold">{c.jumphost?.label || c.jumphost?.dns || c.jumphost?.ip || 'jumphost'}</td>
|
<td className="fw-semibold text-break">{c.jumphost?.label || c.jumphost?.dns || c.jumphost?.ip || 'jumphost'}</td>
|
||||||
<td className="small text-muted">{c.interfaceName || '—'}</td>
|
<td className="small text-muted text-break">{c.interfaceName || '—'}</td>
|
||||||
<td className="small text-muted">
|
<td className="small text-muted text-break">
|
||||||
ping: {c.pingMs ?? '—'} ms, speed: {c.speedMbps ?? '—'} Mbps
|
ping: {c.pingMs ?? '—'} ms, speed: {c.speedMbps ?? '—'} Mbps
|
||||||
</td>
|
</td>
|
||||||
<td className="small">
|
<td className="small">
|
||||||
@@ -187,7 +201,8 @@ export default function RouteOptimizerPage() {
|
|||||||
|
|
||||||
<div className="fw-semibold mb-2">Полные маршруты Home → Jumphost → Exit</div>
|
<div className="fw-semibold mb-2">Полные маршруты Home → Jumphost → Exit</div>
|
||||||
<div className="table-responsive">
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Маршрут</th>
|
<th>Маршрут</th>
|
||||||
@@ -206,7 +221,7 @@ export default function RouteOptimizerPage() {
|
|||||||
</tr>
|
</tr>
|
||||||
) : routes.map((r) => (
|
) : routes.map((r) => (
|
||||||
<tr key={r.id}>
|
<tr key={r.id}>
|
||||||
<td>
|
<td className="text-break">
|
||||||
<span className="fw-semibold">{r.home?.label}</span>
|
<span className="fw-semibold">{r.home?.label}</span>
|
||||||
<span className="text-muted"> → </span>
|
<span className="text-muted"> → </span>
|
||||||
<span className="fw-semibold">{r.jumphost?.label}</span>
|
<span className="fw-semibold">{r.jumphost?.label}</span>
|
||||||
@@ -216,10 +231,10 @@ export default function RouteOptimizerPage() {
|
|||||||
<td>
|
<td>
|
||||||
<span className="badge bg-green-lt text-green">{r.probabilityOptimal}%</span>
|
<span className="badge bg-green-lt text-green">{r.probabilityOptimal}%</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="small text-muted">
|
<td className="small text-muted text-break">
|
||||||
ping: {r.homeToJumphost?.pingMs ?? '—'} ms, speed: {r.homeToJumphost?.speedMbps ?? '—'} Mbps
|
ping: {r.homeToJumphost?.pingMs ?? '—'} ms, speed: {r.homeToJumphost?.speedMbps ?? '—'} Mbps
|
||||||
</td>
|
</td>
|
||||||
<td className="small text-muted">
|
<td className="small text-muted text-break">
|
||||||
ping: {r.jumphostToExit?.pingMs ?? '—'} ms, speed: {r.jumphostToExit?.speedMbps ?? '—'} Mbps
|
ping: {r.jumphostToExit?.pingMs ?? '—'} ms, speed: {r.jumphostToExit?.speedMbps ?? '—'} Mbps
|
||||||
</td>
|
</td>
|
||||||
<td className="small">{r.score}</td>
|
<td className="small">{r.score}</td>
|
||||||
@@ -261,7 +276,8 @@ export default function RouteOptimizerPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="table-responsive">
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Community</th>
|
<th>Community</th>
|
||||||
@@ -280,7 +296,7 @@ export default function RouteOptimizerPage() {
|
|||||||
</tr>
|
</tr>
|
||||||
) : rows.map((r) => (
|
) : rows.map((r) => (
|
||||||
<tr key={`${jh.label}-${r.community}`}>
|
<tr key={`${jh.label}-${r.community}`}>
|
||||||
<td>
|
<td className="text-break">
|
||||||
<div className="fw-semibold">{r.community}</div>
|
<div className="fw-semibold">{r.community}</div>
|
||||||
{(r.communityInfo?.name || r.communityInfo?.description) && (
|
{(r.communityInfo?.name || r.communityInfo?.description) && (
|
||||||
<div className="small text-muted">
|
<div className="small text-muted">
|
||||||
@@ -288,7 +304,7 @@ export default function RouteOptimizerPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="small">
|
<td className="small text-break">
|
||||||
<div>{r.current?.gateway || r.currentGateway || '—'}</div>
|
<div>{r.current?.gateway || r.currentGateway || '—'}</div>
|
||||||
{r.current?.tunnelGateway && (
|
{r.current?.tunnelGateway && (
|
||||||
<div className="text-muted">
|
<div className="text-muted">
|
||||||
@@ -303,7 +319,7 @@ export default function RouteOptimizerPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="small">
|
<td className="small text-break">
|
||||||
<div>{r.recommended?.gateway || '—'}</div>
|
<div>{r.recommended?.gateway || '—'}</div>
|
||||||
{r.pinnedBySettings && (
|
{r.pinnedBySettings && (
|
||||||
<div className="text-muted">
|
<div className="text-muted">
|
||||||
@@ -326,7 +342,7 @@ export default function RouteOptimizerPage() {
|
|||||||
<td className="small">
|
<td className="small">
|
||||||
{fmtProb(r.current?.probabilityOptimal)} / {fmtProb(r.recommended?.probabilityOptimal)}
|
{fmtProb(r.current?.probabilityOptimal)} / {fmtProb(r.recommended?.probabilityOptimal)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td className="text-break">
|
||||||
{r.current == null && r.recommended == null ? (
|
{r.current == null && r.recommended == null ? (
|
||||||
<span className="badge bg-secondary-lt text-secondary">Недостаточно данных</span>
|
<span className="badge bg-secondary-lt text-secondary">Недостаточно данных</span>
|
||||||
) : r.pinnedBySettings && !r.pinnedGatewayResolved ? (
|
) : r.pinnedBySettings && !r.pinnedGatewayResolved ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user