Skip to content

Luci app qosify quick add - #9054

Open
choppyc79 wants to merge 8 commits into
openwrt:masterfrom
choppyc79:luci-app-qosify-quick-add
Open

choppyc79 wants to merge 8 commits into
openwrt:masterfrom
choppyc79:luci-app-qosify-quick-add

Conversation

@choppyc79

@choppyc79 choppyc79 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Pull request details

Description

Depends on #9046, #9047, #9048, #9050, #9051, #9052, #9053. Only the last commit is this PR; draft until those are merged.

Config: Quick Add Config becomes three folding sections (defaults,
class/alias, interface/device), each a section grid with its own type
select and Add button, plus an options panel carrying the qosify README
description of every option. A folding Reference lists classes, DSCP
values and the defaults qosify applies when a key is absent.

Rules: Quick Add Rule is one folding row with the mapping-file syntax and
the class list (with DSCP values) in panels. The tab is titled Rules.

Both editors sit in their own section and are fitted to the window on tab
open, fold and resize, measured on the next animation frame because
ui.tabs.switchTab() fires cbi-tab-active mid-loop. Fold state is kept in
sessionStorage (guarded).

Advanced: Backup, Restore (themed file button over the hidden picker),
Maintenance (Check Devices) and Defaults as separate sections.

No ACL change.

Screenshot or video of changes

<Config folds, Rules, Advanced>

Maintainer

@choppyc79


Tested on

OpenWrt version: SNAPSHOT r36237-2ed2c210c3
LuCI version: 26.257.37274~f4eebd4
Web browser(s): Google Chrome


Checklist

  • (Nice to have) Includes what Issue it closes (e.g. openwrt/luci#issue-number).
  • (Nice to have) Includes what it depends on (e.g. openwrt/packages#pr-number in sister repo).
luci-app-qosify Advanced luci-app-qosify Rules luic-app-qosify Config

choppyc79 and others added 5 commits September 19, 2026 18:38
rpc.declare() resolves a ubus error status unless reject is set, so a
status code (6 once the session's ACL no longer covers the object, 4
when the object is gone) stays in result[0] and expect {'': {}}
rewrites it to the same {} a working call with nothing to report
returns. L.resolveDefault() cannot tell the two apart, so an ACL left
over from an older install, a stale session or a stopped rpcd reads as
a stopped, unshaped qosify with no init script.

Declare rc.list, service.list and qosify.status with reject: true and
catch each rejection to null. running, enabled, hasInit, active and
shaped are then null rather than false when the call was not answered,
and the Overview service table shows an amber Unknown badge for each,
with the cause named once on the Running row. The qosify object goes
with the daemon, so an unanswered status call while qosify is stopped
still reads as not shaping.

The autostart button carries the state as its label, so it shows
Unknown and is disabled while the state is unknown. Its click now reads
the state from the last refresh rather than from when the button was
built, so a tick that changes the state also changes the action. The
start, stop, restart and reload buttons stay clickable and report the
failing call's own error.

A save no longer warns that qosify is not shaping when the check could
not run; it says shaping could not be checked. The Status tab says rpcd
is not answering rather than that qosify is not running, and says the
interface summary is unavailable when only qosify status went
unanswered.

po/templates/qosify.pot regenerated with build/i18n-scan.pl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoEgybb68JiJnqmasx6Sfh
Signed-off-by: Ash Clarke <clarkeaj@hotmail.co.uk>
A rules edit only needs the mapping files re-read, but the only way to
get that from the page is Reload, the init script's reload_service(),
which pushes the whole config. Add Reload Rules next to it: ubus call
qosify reload runs qosify_map_reload(), which re-reads the files in the
defaults list and leaves the qdiscs, the interface config and the
dynamically added entries alone.

Add Check Devices to the Advanced tab: ubus call qosify check_devices
runs qosify_iface_check(), which starts a section whose device now
exists and stops one whose device has gone, so a device that appears
after qosify started is picked up without a restart rebuilding every
qdisc. The method arms a 10 ms uloop timer and returns before the pass
runs, so the page waits before refreshing the service state.

Both methods return an empty reply, so they are declared with
reject: true; without it a failed call would report success. Both are
in qosify 1501e09 (24.10, 25.12) and master. Grant them in the write
ACL group.

po/templates/qosify.pot regenerated with build/i18n-scan.pl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoEgybb68JiJnqmasx6Sfh
Signed-off-by: Ash Clarke <clarkeaj@hotmail.co.uk>
Both pollers pass a fixed 10 s to poll.add(), so the page ignores
luci.main.pollinterval, the setting every other LuCI view that polls
follows. Leave the interval out so poll.add() takes L.env.pollinterval
(5 s unless set).

On the default setting the Status tab now forks qosify-status every
5 s rather than 10 s. It still does so only while that tab is open, and
Poll.step() still holds the next tick until the last one settles, so a
fork slower than the interval skips ticks instead of stacking up. A
target that cannot afford the fork at that rate raises
luci.main.pollinterval, which slows the rest of LuCI's polling with it,
instead of the page choosing its own rate.

Pausing with the header refresh toggle is unchanged, as it stops the
whole poll loop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoEgybb68JiJnqmasx6Sfh
Signed-off-by: Ash Clarke <clarkeaj@hotmail.co.uk>
The page draws its own badges, key/value tables, reference boxes and
muted notes with qos-* classes and inline styles, inside fieldset and
legend sections, so it looks the same under every theme and ignores the
theme's own table, label and section styles. Use the markup the themes
already style instead:

- sections are div.cbi-section with an h3 title
- status and file validity use .label success, warning and danger
- key/value tables are .table with .tr and .td rows
- Quick Settings and the Advanced file rows are .cbi-value rows, with
  hints as .cbi-value-description
- notes are cbi-section-descr, <p> or <em>, and code is <code>

qosify.css now draws each section as a box with a title bar from the
theme's own variables, gives the two editors and the tc output their
size, and lays out the Quick Add rows. Bootstrap and OpenWrt 2020 have
no .label.danger, so the sheet supplies it from --error-color-high.

The editor width moves from the qos-edit class to the textarea ids with
box-sizing: border-box, next to the height, and the editors resize in
both directions, so the theme's textarea width cannot win and a wrong
width stays correctable.

No behaviour or string changes; po/templates/qosify.pot regenerated
with build/i18n-scan.pl for the moved source lines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoEgybb68JiJnqmasx6Sfh
Signed-off-by: Ash Clarke <clarkeaj@hotmail.co.uk>
qosify keeps per-class counters and, on master, per-DNS-pattern and
eBPF map statistics, but the page shows none of them. Add a Counters
tab, polled like the others and only while it is open.

Traffic by Class lists the classes from ubus call qosify get_stats with
their dscp, packets, bytes and share, and a total row. Bar length is
the cube root of each row against the largest, so one bulk flow does
not flatten every other row; the share column stays exact. Rows are
grouped and coloured by the CAKE tin their egress codepoint lands in
under the configured diffserv mode, and AFx1 to AFx3 sort by drop
precedence. The tcp_default and udp_default slots are skipped, as a
dscp_default_* naming a class is already counted against that class.

get_stats has two shapes. qosify master (beeb87e) returns a classes
table plus ebpf_map_entries, last_reload_time, dns_cache, dscp and dns;
1501e09 (24.10, 25.12) returns qosify_map_stats() at the top level, one
table per class with packets only. Both are read, and only the fields
the reply carries are shown.

Traffic by CAKE Tin graphs the qosify-status output tin by tin, egress
and ingress summed from the pkts, bytes and drops rows tc prints, with
marks on hover. Qdiscs running a different diffserv mode get a table of
their own. It needs the existing qosify-status exec grant, so a
read-only session does not get it.

DNS Entries lists the dns entries from ubus call qosify dump with hits,
packets and bytes from the get_stats dns table. It is shown only when
get_stats carries that table, so dump is never called on 1501e09.

While qosify is stopped the figures are cleared rather than left on
screen looking live. get_stats and dump are declared with reject: true,
so a failed call is reported rather than read as an empty reply. Tin
colours use the theme's error, primary, warn and success colours, with
light and dark fallbacks for the extra diffserv8 and precedence tins.

Grant get_stats and dump in the read ACL group.

po/templates/qosify.pot regenerated with build/i18n-scan.pl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoEgybb68JiJnqmasx6Sfh
Signed-off-by: Ash Clarke <clarkeaj@hotmail.co.uk>

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit checks

  • 2c0e03b "luci-app-qosify: fold Quick Add, fit editors, split backup/restore" — the config interface Quick Add also loses its option bandwidth field, which the message does not mention; state the removal and the reason for it.

Generated by Claude Code

this.qaInput(qadIf,'name','option','wan');
this.qaSelect(qadIf,'disabled',['0','1']);
this.qaInput(qadIf,'bandwidth_up','option','100mbit');
this.qaInput(qadIf,'bandwidth_down','option','100mbit');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

option bandwidth was dropped from this grid, so Quick Add can no longer emit it, yet qosify still reads it as the fallback for both directions (add_interface() at qosify.init:56-66) and this page still consumes it in ifCfg() at main.js:261-262 and warns on it at [main.js:284-285](https://github.com/openwrt/luci/blob/2c0e03b93e34832ede8252c1f5e3af21aff261e6/applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js#L284-L285).`` Was the removal intentional, or should the field come back (it also needs an OPT_DESC entry)?


Generated by Claude Code

classRows:function(classes){
if(!classes.length)return emRow(_('No classes defined in %s').format(UCI_PATH));
return classes.map(function(c){
return kvRow(E('code',{},c.name),'ingress %s, egress %s'.format(c.ingress||'-',c.egress||'-')+(c.alias?' (alias)':''));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: both user-visible strings here lost their _()Ingress: %s / Egress: %s and (alias) were translatable before this commit and are now dropped from the .pot. Regenerate the template after re-wrapping.

Suggested change
return kvRow(E('code',{},c.name),'ingress %s, egress %s'.format(c.ingress||'-',c.egress||'-')+(c.alias?' (alias)':''));
return kvRow(E('code',{},c.name),_('ingress %s, egress %s').format(c.ingress||'-',c.egress||'-')+(c.alias?' '+_('(alias)'):''));

Generated by Claude Code

choppyc79 and others added 2 commits September 20, 2026 09:28
The service state is split over the Service Status table, a Service
Controls section, and the per-interface summary at the top of the
Status tab. Merge it into one Service section on Overview: status,
uptime, autostart, the number of shaping interfaces, one row per
interface or device from ubus call qosify status (active state,
resolved device, ingress, egress) and /etc/init.d/qosify. The table
follows the configured interfaces, so a tick swaps it whole; it holds
no input or focus.

Uptime is the qosify process's starttime from /proc/<pid>/stat against
/proc/uptime. Both run on the boot clock, so an NTP step does not skew
it. The start is cached per pid, so a reload keeps counting, a restart
starts again, and ticks read nothing new until the pid changes. Grant
read on /proc/uptime and /proc/[0-9]*/stat.

The service buttons move to a bar at the bottom of Overview. Buttons
that do not apply to the current state are disabled: Start while
running, Restart, Reload and Stop while stopped, and Reload Rules while
the daemon is down. With the state unknown they stay clickable, so the
failing call reports its own error.

The two config files are one table with status, section or rule count,
size and modification time. The Status tab shows only the qosify-status
output and no longer calls qosify status. Counters moves before
Advanced in the tab order.

po/templates/qosify.pot regenerated with build/i18n-scan.pl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoEgybb68JiJnqmasx6Sfh
Signed-off-by: Ash Clarke <clarkeaj@hotmail.co.uk>
Quick Settings is one long form with labels close to the raw UCI
names and hints only on a few fields. Split it into Basic, Shaping,
Overhead and Advanced tabs under one Save & Apply bar, in a section
titled with the section it edits (interface wan quick settings), and
keep the open tab across the redraw after a save.

Every field gets a plain label and a short hint in qosify's own
wording, beside the control. Name is always shown, prefilled from the
section name for a config interface only, as a config device names a
netdev that differs from its section name by convention.

Overhead gains the three keys qosify.init reads that the form did not
offer: overhead_mpu, overhead_vlan and overhead_encap. overhead and
overhead_encap are shown, and saved, only under overhead_type manual,
since qosify.init ignores them otherwise; overhead_mpu and
overhead_vlan always apply. overhead may now be negative, which CAKE
accepts, and overhead_mpu must be a whole number of bytes.

mode no longer offers an empty choice: unset selects diffserv4, the
daemon's own default in interface.c, and a save writes it.

The write path is unchanged: the same setOpts() rewrite of the section
in /etc/config/qosify, followed by a reload.

po/templates/qosify.pot regenerated with build/i18n-scan.pl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoEgybb68JiJnqmasx6Sfh
Signed-off-by: Ash Clarke <clarkeaj@hotmail.co.uk>
@choppyc79
choppyc79 force-pushed the luci-app-qosify-quick-add branch from 2c0e03b to d6ffd72 Compare September 20, 2026 08:29
Quick Add Config is one form that swaps its fields with a type select,
and the option names carry no explanation. Replace it with three
folding sections, one per stanza group (defaults, class/alias,
interface/device), each a section grid with the option names across
the top, its own type select and Add button, and a panel giving every
option the description from the qosify README. A folding Reference
lists the defined classes, the accepted DSCP values and the defaults
qosify applies when a key is absent. The option panels are read back
out of the forms, so the two cannot disagree.

Quick Add Rule becomes one folding row, match, value, dscp, + and Add,
with the mapping file syntax and the defined classes in panels; each
class is listed with its DSCP value. Fold state is kept for the browser
session, with every sessionStorage access guarded.

Each editor sits in its own section, sized so the tab fits the window
and refitted when the tab opens, a section folds or the window resizes.
The fit is measured on the next animation frame: ui.tabs.switchTab()
fires cbi-tab-active from inside its loop over the panes, so panes
after the new one are still active when the handler runs, and a
measurement there counts the tab being left.

Advanced splits into Backup (file, size, modified, Download, the sizes
kept current by the Overview tick), Restore (a LuCI button over the
hidden native file picker, which ignores the theme, with the chosen
name beside it), Maintenance (Check Devices) and Defaults. The tab is
titled Rules rather than Classification Rules.

po/templates/qosify.pot regenerated with build/i18n-scan.pl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoEgybb68JiJnqmasx6Sfh
Signed-off-by: Ash Clarke <clarkeaj@hotmail.co.uk>
@choppyc79
choppyc79 force-pushed the luci-app-qosify-quick-add branch from d6ffd72 to 178ba9a Compare September 20, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants