mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2026-10-24 22:07:40 -05:00
Improve dynamic circuits config UX
- Rework Operation layout to avoid wide-table squeeze\n- Add responsive config table styling\n- Collapse min/max speed columns into down/up pairs
This commit is contained in:
1 parent
6e4bb732ed
commit
5a1e266dd0
3 files changed
+157
-49
No files matched your search
@@ -119,7 +119,7 @@ function renderRangesTable() {
|
||||
if (!Array.isArray(ranges) || ranges.length === 0) {
|
||||
const empty = document.createElement("tr");
|
||||
const cell = document.createElement("td");
|
||||
cell.colSpan = 8;
|
||||
cell.colSpan = 6;
|
||||
cell.className = "text-muted";
|
||||
cell.textContent = "No rules configured.";
|
||||
empty.appendChild(cell);
|
||||
@@ -150,49 +150,91 @@ function renderRangesTable() {
|
||||
return input;
|
||||
};
|
||||
|
||||
const addCell = (child) => {
|
||||
const mkRatePair = (
|
||||
downValue,
|
||||
onDownChange,
|
||||
upValue,
|
||||
onUpChange,
|
||||
) => {
|
||||
const container = document.createElement("div");
|
||||
container.className = "d-grid gap-1";
|
||||
|
||||
const mkDir = (icon, ariaLabel, value, onChange) => {
|
||||
const group = document.createElement("div");
|
||||
group.className = "input-group input-group-sm";
|
||||
|
||||
const prefix = document.createElement("span");
|
||||
prefix.className = "input-group-text";
|
||||
const i = document.createElement("i");
|
||||
i.className = `fa ${icon}`;
|
||||
prefix.appendChild(i);
|
||||
|
||||
const input = mkNumber(value, onChange);
|
||||
input.setAttribute("aria-label", ariaLabel);
|
||||
|
||||
group.appendChild(prefix);
|
||||
group.appendChild(input);
|
||||
return group;
|
||||
};
|
||||
|
||||
container.appendChild(mkDir("fa-arrow-down", "Down Mbps", downValue, onDownChange));
|
||||
container.appendChild(mkDir("fa-arrow-up", "Up Mbps", upValue, onUpChange));
|
||||
return container;
|
||||
};
|
||||
|
||||
const addCell = (label, child) => {
|
||||
const td = document.createElement("td");
|
||||
td.dataset.label = label;
|
||||
td.appendChild(child);
|
||||
tr.appendChild(td);
|
||||
};
|
||||
|
||||
addCell(
|
||||
"Name",
|
||||
mkText(rule.name, (v) => {
|
||||
window.config.dynamic_circuits.ranges[index].name = v;
|
||||
}, "Rule name"),
|
||||
);
|
||||
addCell(
|
||||
"IP Range",
|
||||
mkText(rule.ip_range, (v) => {
|
||||
window.config.dynamic_circuits.ranges[index].ip_range = v;
|
||||
}, "192.168.0.0/24"),
|
||||
);
|
||||
addCell(
|
||||
mkNumber(rule.download_min_mbps, (v) => {
|
||||
window.config.dynamic_circuits.ranges[index].download_min_mbps = v;
|
||||
}),
|
||||
"Min Mbps",
|
||||
mkRatePair(
|
||||
rule.download_min_mbps,
|
||||
(v) => {
|
||||
window.config.dynamic_circuits.ranges[index].download_min_mbps = v;
|
||||
},
|
||||
rule.upload_min_mbps,
|
||||
(v) => {
|
||||
window.config.dynamic_circuits.ranges[index].upload_min_mbps = v;
|
||||
},
|
||||
),
|
||||
);
|
||||
addCell(
|
||||
mkNumber(rule.upload_min_mbps, (v) => {
|
||||
window.config.dynamic_circuits.ranges[index].upload_min_mbps = v;
|
||||
}),
|
||||
);
|
||||
addCell(
|
||||
mkNumber(rule.download_max_mbps, (v) => {
|
||||
window.config.dynamic_circuits.ranges[index].download_max_mbps = v;
|
||||
}),
|
||||
);
|
||||
addCell(
|
||||
mkNumber(rule.upload_max_mbps, (v) => {
|
||||
window.config.dynamic_circuits.ranges[index].upload_max_mbps = v;
|
||||
}),
|
||||
"Max Mbps",
|
||||
mkRatePair(
|
||||
rule.download_max_mbps,
|
||||
(v) => {
|
||||
window.config.dynamic_circuits.ranges[index].download_max_mbps = v;
|
||||
},
|
||||
rule.upload_max_mbps,
|
||||
(v) => {
|
||||
window.config.dynamic_circuits.ranges[index].upload_max_mbps = v;
|
||||
},
|
||||
),
|
||||
);
|
||||
const attach = mkText(rule.attach_to, (v) => {
|
||||
window.config.dynamic_circuits.ranges[index].attach_to = v;
|
||||
}, "network.json node (optional)");
|
||||
attach.setAttribute("list", "networkNodesDatalist");
|
||||
addCell(attach);
|
||||
addCell("Attach To", attach);
|
||||
|
||||
const removeTd = document.createElement("td");
|
||||
removeTd.dataset.label = "Actions";
|
||||
const removeBtn = document.createElement("button");
|
||||
removeBtn.type = "button";
|
||||
removeBtn.className = "btn btn-sm btn-outline-danger";
|
||||
|
||||
@@ -13,21 +13,28 @@
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="lqos-config-section h-100">
|
||||
<div class="col-12">
|
||||
<div class="lqos-config-section">
|
||||
<h6 class="lqos-config-section-title">Operation</h6>
|
||||
<div class="lqos-config-section-subtitle">Enable the subsystem and choose retention and promotion behavior.</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="dynamicCircuitsEnabled">
|
||||
<label class="form-check-label" for="dynamicCircuitsEnabled">Enable Dynamic Circuits</label>
|
||||
<div class="form-text">Master switch for dynamic circuit tracking and promotion.</div>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="mb-0 form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="dynamicCircuitsEnabled">
|
||||
<label class="form-check-label" for="dynamicCircuitsEnabled">Enable Dynamic Circuits</label>
|
||||
<div class="form-text">Master switch for dynamic circuit tracking and promotion.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="dynamicCircuitsTtlSeconds" class="form-label">Time to Live (seconds)</label>
|
||||
<input type="number" class="form-control" id="dynamicCircuitsTtlSeconds" min="1" step="1" value="300">
|
||||
<div class="form-text">How long dynamic circuit state remains valid without being seen.</div>
|
||||
<div class="col-12 col-xl-6">
|
||||
<label for="dynamicCircuitsTtlSeconds" class="form-label">Time to Live</label>
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="dynamicCircuitsTtlSeconds" min="1" step="1" value="300">
|
||||
<span class="input-group-text">seconds</span>
|
||||
</div>
|
||||
<div class="form-text">How long dynamic circuit state remains valid without being seen.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,37 +44,48 @@
|
||||
<h6 class="lqos-config-section-title">Range Rules</h6>
|
||||
<div class="lqos-config-section-subtitle">Define the IP networks that should receive default rates and an optional topology attachment.</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="unknownIpPromotionEnabled">
|
||||
<label class="form-check-label" for="unknownIpPromotionEnabled">Enable Unknown IP Promotion</label>
|
||||
<div class="form-text">When enabled, unknown IP traffic is evaluated against the range rules below.</div>
|
||||
<div class="d-flex align-items-start justify-content-between flex-wrap gap-3 mb-3">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input type="checkbox" class="form-check-input" id="unknownIpPromotionEnabled">
|
||||
<label class="form-check-label" for="unknownIpPromotionEnabled">Enable Unknown IP Promotion</label>
|
||||
<div class="form-text">When enabled, unknown IP traffic is evaluated against the range rules below.</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button type="button" id="addDynamicRange" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fa fa-plus me-1"></i>Add Rule
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<datalist id="networkNodesDatalist"></datalist>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<table class="table table-sm table-hover align-middle lqos-config-responsive-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="min-width: 12rem;">Name</th>
|
||||
<th style="min-width: 12rem;">IP Range</th>
|
||||
<th style="min-width: 8rem;">Min Down</th>
|
||||
<th style="min-width: 8rem;">Min Up</th>
|
||||
<th style="min-width: 8rem;">Max Down</th>
|
||||
<th style="min-width: 8rem;">Max Up</th>
|
||||
<th style="min-width: 12rem;">Attach To</th>
|
||||
<th style="min-width: 6rem;">Actions</th>
|
||||
<th style="min-width: 9rem;">Name</th>
|
||||
<th style="min-width: 10rem;">
|
||||
IP Range
|
||||
<span
|
||||
class="ms-1 text-muted"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="IP range help"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="CIDR notation (for example 192.168.200.0/24). 0.0.0.0 and :: mean match-all."
|
||||
><i class="fas fa-info-circle"></i></span>
|
||||
</th>
|
||||
<th style="min-width: 9rem;">Min Mbps</th>
|
||||
<th style="min-width: 9rem;">Max Mbps</th>
|
||||
<th style="min-width: 11rem;">Attach To</th>
|
||||
<th style="min-width: 5rem;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dynamicRangesBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="button" id="addDynamicRange" class="btn btn-outline-primary btn-sm">Add Rule</button>
|
||||
|
||||
<div class="form-text mt-2">
|
||||
IP Range accepts CIDR notation (for example <code>192.168.200.0/24</code>). <code>0.0.0.0</code> and <code>::</code> are allowed and mean match-all.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1903,6 +1903,54 @@ table tr td a {
|
||||
color: var(--bs-secondary-color) !important;
|
||||
}
|
||||
|
||||
/* Responsive inline tables for config editors */
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.lqos-config-responsive-table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lqos-config-responsive-table tbody tr {
|
||||
display: block;
|
||||
border: 1px solid var(--lqos-border);
|
||||
border-radius: var(--lqos-radius-md);
|
||||
background: color-mix(in srgb, var(--lqos-surface) 70%, transparent);
|
||||
padding: 0.65rem 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.lqos-config-responsive-table tbody td {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding: 0.35rem 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.lqos-config-responsive-table tbody td::before {
|
||||
content: attr(data-label);
|
||||
flex: 0 0 7.25rem;
|
||||
color: var(--bs-secondary-color);
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.lqos-config-responsive-table tbody td > * {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.lqos-config-responsive-table tbody td[colspan] {
|
||||
display: block;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.lqos-config-responsive-table tbody td[colspan]::before {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.lqos-config-network-editor {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
|
||||
Reference in new issue
Block a user