Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1afb2d4
Implementing Start _ Stop for QoS
ExtremeFiretop Aug 4, 2025
c322de2
Adding Menu Options
ExtremeFiretop Aug 5, 2025
fa30b5a
More Cleanup
ExtremeFiretop Aug 5, 2025
fe469fc
Implementing WebUI Schedule Control
ExtremeFiretop Aug 5, 2025
8197d8b
Connecting WebUI and Shell
ExtremeFiretop Aug 5, 2025
6d42111
Additional Cleanup
ExtremeFiretop Aug 5, 2025
d8a104c
Adding Missing Startup Entry
ExtremeFiretop Aug 5, 2025
9a03626
Fine Tuning
ExtremeFiretop Aug 6, 2025
2f7436a
Fixing Bug/Typo
ExtremeFiretop Aug 6, 2025
a855e57
Bug Fix
ExtremeFiretop Aug 6, 2025
387dc39
Immediately Trigger for Past Due Schedules
ExtremeFiretop Aug 7, 2025
d7d37ee
Allow Custom Cron Jobs
ExtremeFiretop Aug 8, 2025
0673a97
Fix Input Validators and Re-Implement Immediate Trigger for Past Due …
ExtremeFiretop Aug 8, 2025
a6116a1
Cleanup
ExtremeFiretop Aug 8, 2025
b071d9f
Restart Firewall and Flush Conntrack
ExtremeFiretop Aug 10, 2025
64e8ba8
Bump actions/checkout from 4.2.2 to 4.3.0 in the all-actions group
dependabot[bot] Aug 11, 2025
4de2a43
Merge pull request #17 from AMTM-OSR/dependabot/github_actions/develo…
ExtremeFiretop Aug 11, 2025
1a253c8
Allow Multiple Schedules
ExtremeFiretop Aug 15, 2025
630bdec
Allow Edits of Single Schedules
ExtremeFiretop Aug 24, 2025
7a9829b
Remove Default Schedule
ExtremeFiretop Aug 24, 2025
1c6b212
Keep Schedule Enabled On Single Delete
ExtremeFiretop Aug 24, 2025
5d029bd
Cleanup
ExtremeFiretop Aug 24, 2025
d94a343
Change Schedule Location
ExtremeFiretop Aug 25, 2025
7d992e9
Merge pull request #16 from AMTM-OSR/Start_Stop
ExtremeFiretop Aug 25, 2025
7cf0032
Bump actions/checkout from 4.3.0 to 5.0.0 in the all-actions group
dependabot[bot] Aug 26, 2025
1245cae
Merge pull request #18 from AMTM-OSR/dependabot/github_actions/develo…
ExtremeFiretop Aug 26, 2025
7b4cdd9
Fixing Code for Minute Parsing
ExtremeFiretop Aug 26, 2025
f483b33
Merge branch 'master' into develop
ExtremeFiretop Sep 4, 2025
43de764
Update readme
ExtremeFiretop Sep 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/Create-NewReleases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
steps:
# 1--- Check out master so we tag the exact merge commit
- name: Checkout source code
uses: actions/checkout@v4.2.2
uses: actions/checkout@v5.0.0
with:
fetch-depth: 0
ref: 'master'
Expand Down
5 changes: 4 additions & 1 deletion Changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
v1.4.9 (04-Sep-2025)
- NEW: Includes Flexible/Dynamic Scheduling of the QoS system on ASUS routers. As requested here: https://www.snbforums.com/threads/flexqos-1-4-8-flexible-qos-enhancement-script-for-adaptive-qos.94976/post-964632

v1.4.8 (18-May-2025)
- CHANGED: Changed Soft Warning for Hard Block on WiFi 7 devices as per dave and RMerlin conversation here: https://www.snbforums.com/threads/asuswrt-merlin-3006-102-5-beta-is-now-available.95115/post-961105
- CHANGED: Added a force uninstall for routers with FlexQoS installed on BE devixes while rebooting/startup.
- CHANGED: Added a force uninstall for routers with FlexQoS installed on BE devices while rebooting/startup.

v1.4.7 (15-June-2025)
- CHANGED: Re-enable develop branch for script development.
Expand Down
301 changes: 300 additions & 1 deletion flexqos.asp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,19 @@ box-shadow: #2B6692 5px 0px 0px 0px inset;
td.cat7{
box-shadow: #6C604F 5px 0px 0px 0px inset;
}
#sched_block thead th {
color: #fff !important;
}
#sched_block .sched-day{
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
margin: 2px 8px 2px 0;
}
#sched_block .sched-daycb{
margin: 0;
}
</style>

<script>
Expand Down Expand Up @@ -544,6 +557,222 @@ function draw_conntrack_table() {
updateTable();
}

// ---- SCHEDULER UI (AppDB-like add/edit/remove) ----
var SCHED_DAYS = ["Su","Mo","Tu","We","Th","Fr","Sa"];
var SCHED_DEFAULT = { days:[1,2,3,4,5], start:"07:00", end:"20:00" };
var SCHED_MAX = 6;

// Internal array of windows: [{days:[0-6], start:"HH:MM", end:"HH:MM"}]
var SCHED = [];

// ---------- helpers ----------
function _sched_trim(s){ return (s||"").toString().trim(); }

function parseDaysSpec(spec){
if (typeof spec !== "string") return [];
spec = spec.trim(); if (!spec) return [];
if (spec === "*") return [0,1,2,3,4,5,6];

var days = [], push = function(n){
if (n === 7) n = 0;
if (n >= 0 && n <= 6 && days.indexOf(n) < 0) days.push(n);
};

spec.split(",").forEach(function(tok){
tok = tok.trim(); if (!tok) return;
if (tok.indexOf("-") >= 0){
var p = tok.split("-"), a = parseInt(p[0],10), b = parseInt(p[1],10);
if (!isNaN(a) && !isNaN(b)){
var lo = Math.min(a,b), hi = Math.max(a,b);
for (var d = lo; d <= hi; d++) push(d);
}
}else{
var n = parseInt(tok,10); if (!isNaN(n)) push(n);
}
});
return days.sort(function(a,b){ return a - b; });
}

function stringifyDays(days){
var uniq = Array.from(new Set((days||[]).map(function(n){ return parseInt(n,10); })))
.filter(function(n){ return n >= 0 && n <= 6; })
.sort(function(a,b){ return a - b; });
if (uniq.length === 7) return "0-6";
return uniq.join(",");
}

function labelDays(days){
var a = (days||[]).slice().sort(function(x,y){return x-y;});
var all = "Every day", wk = [1,2,3,4,5].toString(), we = [0,6].toString();
if (a.length === 7) return all;
if (a.toString() === wk) return "Weekdays (Mo–Fr)";
if (a.toString() === we) return "Weekends (Su,Sa)";
return a.map(function(d){ return SCHED_DAYS[d]; }).join(", ");
}

function timeValid(hm){
return /^[0-2]\d:[0-5]\d$/.test(hm) && parseInt(hm.substr(0,2),10) < 24;
}

// Parse one record string (“<1>DOW>HH:MM>HH:MM” or 3-part)
function schedParse(str){
if (typeof str !== "string" || !str.trim()) return null;
var s = str.replace(/^</,"").replace(/>$/,"").split(">");
if (s.length < 3 || s.length > 4) return null;
var en = (s[0] === "1");
var days = parseDaysSpec(s[1]);
var o = { enabled: en, days: days };
if (s.length === 4){ o.start = s[2]; o.end = s[3]; }
else { // 3-part, treat as start-only (“start” OR “end”)
// Decide by position we saved in the past; to be safe, accept either:
if (timeValid(s[2])) o.start = s[2]; else o.end = s[2];
}
return o;
}

// Stringify one full window record
function schedStringify(win){
return "<1>" + stringifyDays(win.days) + ">" + (win.start||"") + ">" + (win.end||"");
}

// ---------- UI readers/writers ----------
function sched_read_add_form(){
var days = [];
for (var d = 0; d <= 6; d++)
if (document.getElementById("sched_new_day_"+d).checked) days.push(d);

var start = _sched_trim(document.getElementById("sched_new_start").value || "07:00");
var end = _sched_trim(document.getElementById("sched_new_end").value || "20:00");

return { days: days, start: start, end: end };
}

function sched_clear_add_form(){
// don’t reset times (nice UX); just clear days
for (var d = 0; d <= 6; d++)
document.getElementById("sched_new_day_"+d).checked = false;
}

// ---------- CRUD ----------
function sched_add_window(){
if (SCHED.length >= SCHED_MAX) { alert("This table only allows " + SCHED_MAX + " items!"); return; }

var win = sched_read_add_form();
if (!win.days.length) { alert("Please select at least one day."); return; }
if (!timeValid(win.start) || !timeValid(win.end)) { alert("Please enter valid HH:MM times."); return; }

SCHED.push(win);
sched_render_rules();
sched_clear_add_form();
}

function sched_remove_row(btn){
var i = btn.parentNode.parentNode.rowIndex; // header is row 0
// Adjust because we render into an inner table; resolve index robustly:
var tr = btn.closest("tr");
var idx = parseInt(tr.getAttribute("data-idx"), 10);
if (!isNaN(idx)) { SCHED.splice(idx,1); sched_render_rules(); }
}

function sched_edit_row(btn){
var tr = btn.closest("tr");
var idx = parseInt(tr.getAttribute("data-idx"), 10);
if (isNaN(idx)) return;
var win = SCHED[idx];

// Fill the “add row” with values
for (var d = 0; d <= 6; d++)
document.getElementById("sched_new_day_"+d).checked = (win.days.indexOf(d) >= 0);
document.getElementById("sched_new_start").value = win.start;
document.getElementById("sched_new_end").value = win.end;

// Remove current row (same as AppDB edit UX)
SCHED.splice(idx,1);
sched_render_rules();
}

// ---------- rendering ----------
function sched_render_rules(){
var code = '<table width="100%" border="1" cellspacing="0" cellpadding="4" class="list_table" id="sched_rulelist_table">';
if (!SCHED.length){
code += '<tr><td style="color:#FFCC00;" colspan="4">No schedules defined</td></tr>';
}else{
for (var i=0; i<SCHED.length; i++){
var w = SCHED[i];
code += '<tr data-idx="'+i+'">';
code += '<td width="50%">'+ labelDays(w.days) +'</td>';
code += '<td width="18%">'+ w.start +'</td>';
code += '<td width="18%">'+ w.end +'</td>';
code += '<td width="14%"><input class="edit_btn" onclick="sched_edit_row(this);" value=""/>';
code += '<input class="remove_btn" onclick="sched_remove_row(this);" value=""/></td>';
code += '</tr>';
}
}
code += '</table>';
document.getElementById("sched_rules_block").innerHTML = code;
}

// ---------- enable/seed ----------
function schedToggleUI(){
var cb = document.getElementById("sched_enabled");
var block = document.getElementById("sched_block"); // legacy container (may not exist)
var row = document.getElementById("sched_block_row"); // new full-width row
var on = cb && cb.checked;

if (block) block.style.display = on ? "" : "none";
if (row) row.style.display = on ? "" : "none";

if (typeof sched_render_rules === "function") sched_render_rules();
}

function schedInitOnce(){
var cb = document.getElementById("sched_enabled");
if (cb) cb.addEventListener("change", schedToggleUI);
}

// Load from saved settings into SCHED and render
function schedPopulateFromSettings(){
SCHED = [];
var raw = _sched_trim(custom_settings.flexqos_schedule || "");
if (raw){
// Accept both 4-part windows and old paired 3-part records
var parts = raw.split("|");
var pairmap = Object.create(null); // key: days-spec → {start?, end?}
parts.forEach(function(p){
var rec = schedParse(p);
if (!rec || !rec.enabled) return;
var key = stringifyDays(rec.days);
if (rec.start && rec.end){
SCHED.push({days:rec.days, start:rec.start, end:rec.end});
}else{
pairmap[key] = pairmap[key] || {days:rec.days};
if (rec.start) pairmap[key].start = rec.start;
if (rec.end) pairmap[key].end = rec.end;
}
});
Object.keys(pairmap).forEach(function(k){
var r = pairmap[k];
if (r.start || r.end){
SCHED.push({days:r.days, start:r.start || "00:00", end:r.end || "00:00"});
}
});

document.getElementById("sched_enabled").checked = true;
schedToggleUI();
}
sched_render_rules();
}

// Serialize SCHED for saving
function schedSerialize(){
if (!SCHED.length) return "";
var out = [];
for (var i=0;i<SCHED.length;i++){
out.push( schedStringify(SCHED[i]) );
}
return out.join("|");
}

function setsort(newfield) {
if (newfield != sortfield) {
sortdir = 0;
Expand Down Expand Up @@ -772,6 +1001,7 @@ function initial() {
// Setup appdb auto-complete menu
autocomplete(document.getElementById("appdb_search_x"), catdb_label_array);
build_overhead_presets();
schedInitOnce();
if (based_modelid == "RT-AX88U_PRO" || based_modelid == "GT-AX11000_PRO" || based_modelid == "GT-AX6000" || based_modelid == "GT-AXE16000" || based_modelid == "RT-AX86U_PRO" || based_modelid == "XT12" )
document.getElementById('flexqos_fccontrol_tr').style.display = "";
}
Expand Down Expand Up @@ -2136,6 +2366,8 @@ function set_FlexQoS_mod_vars()
document.form.flexqos_outputcls.value = "5";
else
document.form.flexqos_outputcls.value = custom_settings.flexqos_outputcls;
// Restore schedule UI
schedPopulateFromSettings();
}

function FlexQoS_reset_iptables() {
Expand Down Expand Up @@ -2282,7 +2514,14 @@ function FlexQoS_mod_apply() {
}
appdb_rulelist_array += appdb_last_rules;


// ---- Save schedule ----
if (document.getElementById('sched_enabled').checked){
var ser = schedSerialize(); // returns pipe-separated "<1>DOW>HH:MM>HH:MM"
if (!ser){ alert("No schedules defined."); return; }
custom_settings.flexqos_schedule = ser;
}else{
delete custom_settings.flexqos_schedule;
}
if (iptables_rulelist_array.length > 2999) {
alert("Total iptables rules exceeds 2999 bytes! Please delete or consolidate!");
return
Expand Down Expand Up @@ -2843,6 +3082,66 @@ function DelCookie(cookiename){
</td>
</tr>
</table>
<!-- Schedules (full-width section) -->
<table width="100%" border="1" align="center" cellpadding="4" cellspacing="0"
class="FormTable_table" id="sched_section" style="margin-top:10px;">
<thead>
<tr>
<td colspan="4">
QoS Schedules&nbsp;(Max Limit : 6)
<span style="float:right; font-weight:normal;">
<label style="cursor:pointer;">
<input type="checkbox" id="sched_enabled"> Enable
</label>
</span>
</td>
</tr>
</thead>
<tbody>
<tr id="sched_block_row" style="display:none;">
<td colspan="4">
<!-- Add Row -->
<table width="100%" border="1" cellspacing="0" cellpadding="4" class="FormTable_table">
<thead>
<tr>
<td colspan="4">Add schedule window</td>
</tr>
</thead>
<tbody>
<tr>
<th width="50%">Days</th>
<th width="18%">Start</th>
<th width="18%">End</th>
<th width="14%">Edit</th>
</tr>
<tr>
<td>
<div class="sched-day">
<label class="sched-day"><input type="checkbox" class="sched-daycb" id="sched_new_day_0"> Su</label>
<label class="sched-day"><input type="checkbox" class="sched-daycb" id="sched_new_day_1"> Mo</label>
<label class="sched-day"><input type="checkbox" class="sched-daycb" id="sched_new_day_2"> Tu</label>
<label class="sched-day"><input type="checkbox" class="sched-daycb" id="sched_new_day_3"> We</label>
<label class="sched-day"><input type="checkbox" class="sched-daycb" id="sched_new_day_4"> Th</label>
<label class="sched-day"><input type="checkbox" class="sched-daycb" id="sched_new_day_5"> Fr</label>
<label class="sched-day"><input type="checkbox" class="sched-daycb" id="sched_new_day_6"> Sa</label>
</div>
</td>
<td><input id="sched_new_start" type="time" class="input_6_table" value="07:00" style="width: 100px; margin-left: 10px;"></td>
<td><input id="sched_new_end" type="time" class="input_6_table" value="20:00" style="width: 100px; margin-left: 10px;"></td>
<td>
<div><input type="button" id="sched_add_btn" class="add_btn" onclick="sched_add_window();" value=""></div>
</td>
</tr>
</tbody>
</table>

<!-- Existing windows list -->
<div id="sched_rules_block" style="margin-top:10px;"></div>
</td>
</tr>
</tbody>
</table>

<div id="iptables_rules_block"></div>

<table width="100%" border="1" align="center" cellpadding="4" cellspacing="0" class="FormTable_table">
Expand Down
Loading