diff --git a/applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js b/applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js index 59920d647f..4b92f53eb6 100644 --- a/applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js +++ b/applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js @@ -342,6 +342,10 @@ return view.extend({ pane.addEventListener('cbi-tab-active',function(){ self.currentTab=t[0]; try{history.replaceState(null,'','#'+names[t[0]]);}catch(e){} + // The Status tab costs a fork per active interface, so it is fetched + // when it is opened rather than on every page load; initTabGroup fires + // this from a requestAnimationFrame, so the pane is in the DOM. + if(t[0]==='st')self.refreshStatus(); }); group.appendChild(pane); }); @@ -358,10 +362,16 @@ return view.extend({ return root; }, + // Both tabs tick at 10 s. Overview is six ubus calls and no forks; the Status + // tab forks qosify-status, which runs tc twice per active interface, so it is + // the expensive one and does not get a faster tick. Poll.step() holds + // the next tick until the promise this returns settles, and refreshStatus() + // drops an overlapping call, so a fork slower than the interval skips ticks + // instead of stacking up. installPollers:function(){ var self=this; poll.add(function(){if(self.currentTab!=='ov'||self._n)return;return self.refreshOverview();},10); - poll.add(function(){return self.refreshStatus();},5); + poll.add(function(){if(self.currentTab!=='st'||self._n)return;return self.refreshStatus();},10); }, tabOverview:function(ctx){ @@ -565,6 +575,17 @@ return view.extend({ return tbl; }, + updateSvcTable:function(ctx){ + var n=this.svcNodes(ctx),map={init:'qos-svc-init',auto:'qos-svc-auto',run:'qos-svc-run',shaped:'qos-svc-shaped'},k,el; + for(k in map){el=$(map[k]);if(el)dom.content(el,n[k]);} + el=$('qos-btn-auto'); + if(el){ + el.className='cbi-button '+(ctx.enabled?'cbi-button-positive':'cbi-button-negative'); + el.title=ctx.enabled?_('Click to disable autostart'):_('Click to enable autostart'); + dom.content(el,ctx.enabled?_('Enabled'):_('Disabled')); + } + }, + renderCfgFiles:function(ctx){ var rulesN=(ctx.rulesN!=null)?ctx.rulesN:countRules(ctx.rulesText); var cfgOk=(ctx.cfgOk!=null)?ctx.cfgOk:((ctx.cfgRaw||'').length>10&&/(^|\n)config /.test(ctx.cfgRaw||'')); @@ -962,7 +983,11 @@ return view.extend({ tabStatus:function(ctx){ var section=E('div',{'id':'qos-st'}); var fs1=E('fieldset',{'class':'cbi-section'},E('legend',{},_('qosify-status'))); - var body=E('div',{'id':'qos-st-body'}); + var body=E('div',{'id':'qos-st-body'},[ + E('div',{'id':'qos-st-sum'}), + E('pre',{'id':'qos-st-pre','class':'qos-pre','style':'display:none'}), + E('div',{'id':'qos-st-msg'}) + ]); this.fillStatus(body,ctx); fs1.appendChild(body); section.appendChild(fs1); @@ -982,15 +1007,51 @@ return view.extend({ return out; }, + // Runs on every poll tick and twice per open, so only the summary table is + // rebuilt: replacing the
 would throw away the scroll position while it
+	// is being read. ctx.qstatus null means the fork has not returned yet, '' means
+	// it returned nothing -- the two used to look the same on screen.
 	fillStatus:function(body,ctx){
-		dom.content(body,'');
+		var sum=body.querySelector('#qos-st-sum'),pre=body.querySelector('#qos-st-pre'),msg=body.querySelector('#qos-st-msg');
+		if(!sum||!pre||!msg)return;
+		var note=function(t){dom.content(msg,E('p',{'class':'qos-muted'},E('em',{},t)));};
 		if(!ctx.running){
-			body.appendChild(E('div',{'class':'alert-message warning'},_('qosify is not running. Start from the Overview tab.')));
-		}else if(!ctx.qstatus){
-			body.appendChild(E('p',{'style':'opacity:.7'},E('em',{},_('qosify-status returned no output.'))));
-		}else{
-			body.appendChild(E('pre',{'style':'background:#1e1e1e;color:#e0e0e0;padding:12px;border:1px solid #333;border-radius:4px;overflow-x:auto;font-size:12px;line-height:1.5;white-space:pre-wrap'},ctx.qstatus));
+			dom.content(sum,'');
+			pre.style.display='none';
+			dom.content(msg,E('div',{'class':'alert-message warning'},_('qosify is not running. Start from the Overview tab.')));
+			return;
 		}
+		dom.content(sum,this.statusSummary(ctx.status));
+		pre.style.display=ctx.qstatus?'':'none';
+		if(ctx.qstatus){
+			if(pre.textContent!==ctx.qstatus)pre.textContent=ctx.qstatus;
+			dom.content(msg,'');
+		}
+		else if(this.readonly)note(_('The detailed tc output needs write access to this page.'));
+		else if(ctx.qstatus==null)note(_('Reading tc output...'));
+		else note(_('qosify-status returned no output.'));
+	},
+
+	// ubus call qosify status, so the per-interface summary costs no forks
+	statusSummary:function(st){
+		var tbl=E('table',{'class':'qos-kv','width':'100%'}),b=E('tbody');
+		tbl.appendChild(b);
+		['interfaces','devices'].forEach(function(g){
+			var t=st&&st[g],k,e;
+			for(k in t){
+				e=t[k]||{};
+				b.appendChild(E('tr',{},[
+					E('td',{},(g==='devices'?_('device %s'):_('interface %s')).format(k)),
+					E('td',{},[
+						E('span',{'class':'qos-badge '+(e.active?'qos-green':'qos-red')},e.active?_('active'):_('inactive')),
+						E('span',{'class':'qos-muted','style':'margin-left:8px'},
+							_('device: %s, ingress: %s, egress: %s').format(e.ifname||'-',e.ingress?_('yes'):_('no'),e.egress?_('yes'):_('no')))
+					])
+				]));
+			}
+		});
+		if(!b.firstChild)b.appendChild(E('tr',{},E('td',{'class':'qos-muted'},E('em',{},_('qosify has no interfaces or devices configured')))));
+		return tbl;
 	},
 
 	// === Actions ===
@@ -1508,18 +1569,18 @@ return view.extend({
 		});
 	},
 
+	// Poll path: six ubus calls (uci.get and gatherCtx(false)'s five), no shell
+	// forks, and the parts of the page that hold user input or focus are patched
+	// in place rather than rebuilt.
 	refreshOverview:function(){
 		var self=this;
 		self.lock();
 		uci.unload('qosify');
 		return uci.load('qosify').then(function(){
-			return self.gatherCtx();
+			return self.gatherCtx(false);
 		}).then(function(ctx){
-			self.fillSect('qos-svc-sect',self.buildSvcSect(ctx));
+			self.updateSvcTable(ctx);
 			self.fillSect('qos-cfg-sect',self.buildCfgSect(ctx));
-			self.fillSect('qos-ctl-sect',self.buildCtlSect(ctx));
-			var stb=$('qos-st-body');
-			if(stb)self.fillStatus(stb,ctx);
 			var bd=$('q-en-badge');
 			if(bd){
 				var sn=ifSect(),w=(sn&&uci.get('qosify',sn.id))||{};
@@ -1532,22 +1593,30 @@ return view.extend({
 	refreshOverviewFull:function(){
 		var self=this;
 		return self.refreshOverview().then(function(ctx){
+			self.fillSect('qos-svc-sect',self.buildSvcSect(ctx));
+			self.fillSect('qos-ctl-sect',self.buildCtlSect(ctx));
 			self.fillSect('qos-qs-sect',self.buildQsSect(ctx));
 			return ctx;
 		});
 	},
 
 	refreshStatus:function(){
-		if(this.currentTab!=='st')return;
 		var self=this;
+		if(self.currentTab!=='st'||self._st)return Promise.resolve();
+		self._st=true;
+		var ex=self.readonly?Promise.resolve(null):L.resolveDefault(fs.exec('/usr/sbin/qosify-status',[]),null);
 		return Promise.all([
 			L.resolveDefault(callServiceList('qosify'),{}),
-			L.resolveDefault(fs.exec('/usr/sbin/qosify-status',[]),{stdout:''})
+			L.resolveDefault(callQosifyStatus(),{})
 		]).then(function(d){
-			var ctx={running:isRunning(d[0]),qstatus:(d[1]&&d[1].stdout)||''};
+			var ctx={running:isRunning(d[0]),status:d[1]||{},qstatus:self.readonly?'':null};
 			var stb=$('qos-st-body');
 			if(stb)self.fillStatus(stb,ctx);
-		});
+			return ex.then(function(r){
+				ctx.qstatus=self.readonly?'':((r&&r.stdout)||'');
+				if(stb)self.fillStatus(stb,ctx);
+			});
+		}).finally(function(){self._st=false;});
 	},
 
 	// which = 'cfg' | 'rules' | undefined: the editor for the file just written is
diff --git a/applications/luci-app-qosify/po/templates/qosify.pot b/applications/luci-app-qosify/po/templates/qosify.pot
index ae7ce7dfd2..3e8392f2eb 100644
--- a/applications/luci-app-qosify/po/templates/qosify.pot
+++ b/applications/luci-app-qosify/po/templates/qosify.pot
@@ -1,148 +1,250 @@
 msgid ""
 msgstr "Content-Type: text/plain; charset=UTF-8"
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:121
-msgid "%s contains a quote — qosify rejects the whole value"
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:563
+msgid "%d interface"
+msgid_plural "%d interfaces"
+msgstr[0] ""
+msgstr[1] ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:604
+msgid "%d rule"
+msgid_plural "%d rules"
+msgstr[0] ""
+msgstr[1] ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:420
+msgid "%s (current)"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1138
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1403
+msgid ""
+"%s and %s are replaced with the templates shipped by the qosify package, and "
+"shaping is left disabled."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1140
+msgid ""
+"%s came back empty although it is %d bytes on disk — refusing to overwrite it"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1645
+msgid "%s changed on disk — your unsaved edits are still in the editor."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:177
+msgid ""
+"%s contains shell metacharacters — qosify assembles the tc command as a "
+"string and runs it with sh -c, so the command will break or execute them"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1564
+msgid ""
+"%s could not be read — the editor is left empty and will not be saved over "
+"it."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1164
+msgid ""
+"%s has changed since this editor was loaded. Saving now discards those "
+"changes."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1193
+msgid ""
+"%s is %d bytes on disk but was never loaded into the editor — refusing to "
+"truncate it. Reload the page first."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:217
+msgid "%s is a list in this section — edit %s directly"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:172
+msgid ""
+"%s is set to \"%s\" — qosify converts it with atoi(), so anything but a non-"
+"zero number means off"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1386
 msgid "%s uploaded, qosify reloaded."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1038
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1421
+msgid "%s was not written: %s"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1424
+msgid "%s was reset but %s was not written: %s"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1266
 msgid "%s, applied (QoS disabled)."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1040
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1268
 msgid "%s, applied."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:43
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:60
 msgid "(alias)"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:338
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:403
 msgid "(unnamed section)"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1244
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1512
 msgid "A config defaults section already exists."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:451
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1510
+msgid "A section name may only contain letters, digits and underscores."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:545
 msgid "Active"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:512
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:773
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:627
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:897
 msgid "Add"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:241
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:336
 msgid "Advanced"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:392
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1199
+msgid "An empty %s stops all shaping. Continue?"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:461
 msgid "Autorate Ingress"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:465
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:572
 msgid "Autostart"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:464
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:560
 msgid "Available"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:740
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:864
 msgid "Available Classes"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:799
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:924
 msgid "Backup Current Files"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:372
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:441
 msgid "Bandwidth Down"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:371
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:440
 msgid "Bandwidth Up"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:62
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1098
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:113
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1347
 msgid "Binary content rejected"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:241
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:733
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:296
+msgid "Cancel"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:335
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:857
 msgid "Classification Rules"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:596
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:785
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1072
+msgid "Cleanup exited with code %d"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:710
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:910
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1308
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1314
 msgid "Clear"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1072
-msgid "Clear config editor? Content will not be saved until you click Save."
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1198
+msgid "Clear configuration"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1076
-msgid "Clear rules editor? Content will not be saved until you click Save."
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1308
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1314
+msgid "Clear editor"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:408
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:478
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:584
 msgid "Click to disable autostart"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:408
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:478
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:584
 msgid "Click to enable autostart"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:338
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:403
 msgid "Common shaping settings — written straight to %s, section %s."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:241
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:497
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:334
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:612
 msgid "Config"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:567
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:682
 msgid "Config Reference"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:995
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1222
 msgid "Config cleared, qosify stopped."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1014
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1222
+msgid "Config cleared."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1243
 msgid "Config saved"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1110
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1118
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1359
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1367
 msgid "Config: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:326
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:391
 msgid "Configuration Files"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:581
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:298
+msgid "Continue"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:977
+msgid "Could not read %s: %s"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:693
 msgid ""
 "DSCP codepoints: CS0–CS7, AF11–AF43, EF, VA, NQB, LE, DF. Any dscp_* value "
 "may also name a class. Prefix with + to override only when the DSCP field is "
 "zero."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:734
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:858
 msgid "DSCP mapping rules loaded by qosify on startup."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:583
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:695
 msgid ""
 "Defaults qosify applies when a key is absent — interface: mode diffserv4, "
 "ingress 1, egress 1, nat 1, host_isolate 1, autorate_ingress 0. device: "
@@ -151,108 +253,109 @@ msgid ""
 "prio_max_avg_pkt_len 0 (disabled)."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:410
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:454
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:465
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:480
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:548
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:561
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:585
 msgid "Disabled"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:846
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:979
 msgid "Download"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:800
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:925
 msgid "Download current config files before making changes."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1352
-msgid "Editors reloaded from disk — unsaved editor changes were discarded."
-msgstr ""
-
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:378
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:447
 msgid "Egress"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:394
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:463
 msgid "Egress Options"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:982
-msgid "Empty config will stop qosify. Continue?"
-msgstr ""
-
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1089
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1338
 msgid "Empty file"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:410
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:465
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1308
+msgid ""
+"Empty the config editor? Nothing is written until you click Save & Apply."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1314
+msgid ""
+"Empty the rules editor? Nothing is written until you click Save & Apply."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:480
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:561
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:585
 msgid "Enabled"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:453
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:547
 msgid "Enabled — Not Running"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:452
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:546
 msgid "Enabled — Not Shaping (check config)"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1242
-msgid "Enter a section name (alphanumeric/underscore)."
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1509
+msgid "Enter a section name."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1209
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1475
 msgid "Enter a single IPv4 address (qosify does not accept CIDR)."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1211
-msgid "Enter a single IPv6 address (qosify does not accept CIDR)."
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1478
+msgid ""
+"Enter a single IPv6 address (qosify does not accept CIDR or a %zone suffix)."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1198
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1459
 msgid "Enter a value."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1051
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1279
 msgid "Error: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1002
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1176
 msgid "Error: No valid config stanzas found."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:921
-msgid "Error: bandwidth_down must look like 100mbit"
-msgstr ""
-
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:920
-msgid "Error: bandwidth_up must look like 100mbit"
-msgstr ""
-
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:917
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1098
 msgid ""
 "Error: invalid characters in options fields. Use alphanumeric, spaces, "
 "hyphens, dots, colons only."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:948
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1129
 msgid "Error: name must be a device or interface name"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:922
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1103
 msgid "Error: overhead must be a whole number of bytes"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1139
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1387
 msgid "Errors:"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1090
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1163
+msgid "File changed on disk"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1339
 msgid "File too large (max 64KB)"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:483
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:598
 msgid "Found (empty or invalid)"
 msgstr ""
 
@@ -260,464 +363,580 @@ msgstr ""
 msgid "Grant access to LuCI app qosify"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:391
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:460
 msgid "Host Isolate"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "IPv4 address"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "IPv6 address"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:377
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:446
 msgid "Ingress"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:393
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:462
 msgid "Ingress Options"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:44
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:61
 msgid "Ingress: %s / Egress: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:464
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:571
 msgid "Init Script"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:463
-msgid "Installed"
-msgstr ""
-
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:368
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:437
 msgid "Interface Name"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:68
-msgid "Invalid rule line: %s"
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:119
+msgid ""
+"Line %d is longer than 1023 characters — the rule loader reads fixed-size "
+"lines and would split it"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:464
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:484
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:560
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:599
 msgid "Missing"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:390
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:459
 msgid "NAT"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:368
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:437
 msgid "Netdev Name"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:714
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:751
-msgid "No classes defined in /etc/config/qosify"
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:253
+msgid "No DSCP target on line %s — qosify skips single-field lines"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1199
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:277
+msgid "No classes defined in %s"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1460
 msgid "No classes defined. Add classes in the Config tab first."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:654
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:779
 msgid "No config defaults section defined"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1084
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1324
 msgid "No files selected."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1206
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1472
 msgid "No spaces or # allowed in patterns or addresses."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1099
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1348
 msgid "No valid UCI config stanzas"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:469
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:558
 msgid "Not Running"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:463
-msgid "Not installed"
-msgstr ""
-
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:924
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1105
 msgid "Note: bandwidth not set — CAKE will run unlimited on that direction."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:395
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:464
 msgid "Options"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:374
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:443
 msgid "Overhead Bytes"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:373
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:442
 msgid "Overhead Type"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:241
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:333
 msgid "Overview"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:463
-msgid "Package"
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1165
+msgid "Overwrite"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1204
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1325
+msgid "Overwrite config files"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1468
 msgid "Port must be 1-65534 (qosify rejects 65535)."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1201
-msgid "Port must be a number or range (e.g. 4500 or 5060-5061)."
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1464
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1467
+msgid "Port must be a number or a range (4500, 5060-5061)."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:755
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:879
 msgid ""
 "Prefix with + to override only when the DSCP field is zero. Ports: tcp:443, "
 "udp:3074, ranges: tcp:5060-5061 (1-65534). DNS: dns:*teams*, regex: dns:/"
 "zoom[0-9]+, CNAME-only: dns_c:. IP: 1.1.1.1, ff01::1"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:358
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:427
 msgid "QoS Enabled"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:376
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:445
 msgid "Queue Mode"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:505
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:620
 msgid "Quick Add Config"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:760
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:884
 msgid "Quick Add Rule"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:336
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:401
 msgid "Quick Settings"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1205
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1471
 msgid "Range start must not exceed end."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1093
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1342
 msgid "Read error"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1103
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1352
 msgid "Reading and validating files..."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:415
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1031
+msgid "Reading tc output..."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:486
 msgid "Reload"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:829
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:954
 msgid ""
 "Replaces both config files with qosify defaults, qosify will be disabled."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1178
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1404
+msgid "Reset"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1438
 msgid "Reset failed: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1154
-msgid "Reset qosify config to defaults?"
-msgstr ""
-
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:831
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:956
 msgid "Reset to Defaults"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1174
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1402
+msgid "Reset to defaults"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1434
 msgid "Reset to defaults, applied."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:828
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:953
 msgid "Reset to qosify Defaults"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1156
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1413
 msgid "Resetting"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:415
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:486
 msgid "Restart"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1156
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1413
 msgid "Restoring defaults..."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1058
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1294
 msgid "Rules saved"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1121
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1125
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1370
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1373
 msgid "Rules: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:470
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:573
 msgid "Running"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:467
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:556
 msgid "Running & Shaping"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:468
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:557
 msgid "Running — Not Shaping"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:607
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:728
 msgid "Same options as class — gives an existing class a second name."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:609
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:730
 msgid ""
 "Same options as interface, but name is a netdev. nat defaults to 0 here and "
 "to 1 for interfaces."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:398
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:597
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:786
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:822
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:467
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:711
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:911
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:947
 msgid "Save & Apply"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:972
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:998
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1023
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1067
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1156
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1225
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1252
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1303
 msgid "Save failed: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:953
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1005
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1054
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1134
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1234
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1290
 msgid "Saving"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:953
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1134
 msgid "Saving settings and applying..."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1245
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1513
 msgid "Section %s already exists."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:606
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:727
 msgid ""
 "Section name is the class name that rules and dscp_* values refer to. value "
 "sets ingress and egress together."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:809
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:934
 msgid "Select files and click Save & Apply to overwrite and restart qosify."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:889
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1062
 msgid "Sending %s to qosify..."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:404
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:473
 msgid "Service Controls"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:322
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:387
 msgid "Service Status"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:900
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1080
 msgid "Service action failed: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:964
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1148
 msgid "Settings saved"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:415
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:574
+msgid "Shaping"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:486
 msgid "Start"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:241
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:337
 msgid "Status"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:415
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:486
 msgid "Stop"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:238
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1030
+msgid "The detailed tc output needs write access to this page."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:321
+msgid ""
+"The qosify UCI configuration could not be loaded — class and interface lists "
+"may be incomplete."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1326
+msgid ""
+"The selected files replace the ones on the router and qosify is reloaded. "
+"Download a backup from this tab first if you need one."
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:326
 msgid "Traffic shaping and DSCP classification via qosify"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:498
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:613
 msgid "UCI configuration — classes, interfaces, defaults."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:184
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:254
 msgid "Unknown class/DSCP target: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:808
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:933
 msgid "Upload Config Files"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1085
-msgid "Upload and overwrite config files? qosify will reload."
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1327
+msgid "Upload and apply"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1130
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1378
 msgid "Upload error: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1148
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1396
 msgid "Upload failed: %s"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1103
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1352
 msgid "Uploading"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:482
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:597
 msgid "Valid"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1041
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1269
 msgid "Warning: %s but qosify is not shaping traffic — check the Status tab."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:889
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1062
 msgid "Working"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1005
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1199
+msgid "Write empty file"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1234
 msgid "Writing config and reloading qosify..."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1054
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1290
 msgid "Writing rules and reloading qosify..."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:273
-msgid "You have unsaved changes. Leave this tab?"
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:358
+msgid ""
+"You have read-only access to this page, so editing and service control are "
+"disabled."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1046
+msgid "active"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1102
+msgid ""
+"bandwidth_down does not look like a tc rate (100mbit, 12MBps, unlimited) — "
+"passing it through anyway"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1101
+msgid ""
+"bandwidth_up does not look like a tc rate (100mbit, 12MBps, unlimited) — "
+"passing it through anyway"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1044
+msgid "device %s"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1048
+msgid "device: %s, ingress: %s, egress: %s"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:175
+msgid ""
+"disabled is set to \"%s\" — config_get_bool does not recognise that, so the "
+"section stays enabled"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "dns pattern"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "dns regex"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "dns_c pattern"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "dns_c regex"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:117
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:438
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:440
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:441
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:462
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:463
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:464
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:891
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1447
+msgid "e.g. %s"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1046
+msgid "inactive"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:168
 msgid "ingress and egress are both 0 — nothing is shaped"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:109
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1044
+msgid "interface %s"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:443
+msgid "manual only"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:160
 msgid ""
 "name is not set — qosify.init sends an empty device name and this section is "
 "never applied"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:608
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:729
 msgid ""
 "name is the netifd interface. bandwidth applies only where bandwidth_up/"
 "bandwidth_down are unset. overhead and overhead_encap apply only when "
 "overhead_type is manual."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:113
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:164
 msgid ""
 "nat is not sent: qosify only emits nat/nonat inside the host_isolate branch. "
 "CAKE does accept flows plus nat — put nat in options to apply it"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:114
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:165
 msgid ""
 "nat only reaches %s — put it in options, or in both ingress_options and "
 "egress_options"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:119
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1048
+msgid "no"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:170
 msgid "no bandwidth_down or bandwidth — ingress CAKE runs unlimited"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:118
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:169
 msgid "no bandwidth_up or bandwidth — egress CAKE runs unlimited"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:772
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:563
+msgid "none"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:896
 msgid "only if unset (+)"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:116
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:167
 msgid "overhead and overhead_encap are ignored unless overhead_type is manual"
 msgstr ""
 
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:325
 #: applications/luci-app-qosify/root/usr/share/luci/menu.d/luci-app-qosify.json:3
 msgid "qosify"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:876
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:538
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1066
+msgid "qosify did not come up — check the system log"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1053
+msgid "qosify has no interfaces or devices configured"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1021
 msgid "qosify is not running. Start from the Overview tab."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:384
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1070
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1215
+msgid "qosify is still running — leaving the qdiscs alone"
+msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:453
 msgid ""
 "qosify only passes this to CAKE together with Host Isolate — add nat to "
 "Options to force it"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:852
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:985
 msgid "qosify-status"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:878
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1032
 msgid "qosify-status returned no output."
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:370
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:439
 msgid "required — qosify skips sections with no name"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:489
-msgid "rules"
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:626
+msgid "section name"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "tcp port"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "tcp+udp port"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:763
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:887
 msgid "udp port"
 msgstr ""
 
-#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:375
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:444
 msgid "used only when Overhead Type is manual"
 msgstr ""
+
+#: applications/luci-app-qosify/htdocs/luci-static/resources/view/qosify/main.js:1048
+msgid "yes"
+msgstr ""