💋 Sync 2026-10-04 04:16:09

This commit is contained in:
github-actions[bot] committed 2026-10-04 04:16:09 +08:00
1 parent f6b2df73ec
commit f62992fd80
745 files changed
+212416 -1173

No files matched your search

@@ -8,12 +8,14 @@
* Filter-chip DOM renderer for luci-app-fwlive.
*
* renderFilterChips(host, state, callbacks) → void
* host - container element (cleared and rebuilt; element itself is kept)
* host - stable container element; unchanged strips keep their nodes
* state - shallow copy: { filters, chipFields }
* callbacks - { onInvert(field, ev), onClear(field, ev), onClearAll(ev) }
* callbacks - { onInvert(field, ev), onClear(field, ev), onClearAll(ev), onFocusFallback(field, action) }
*
* Chips use the labels presentation (include: "is"/"contains", exclude: "not"/"does not contain" + light ≠).
* Modules must not mutate state. host is cleared then rebuilt (idempotent replace).
* Modules must not mutate state. Changed strips are replaced; chip focus follows
* the same field/action or moves to Clear all/the associated filter (a null
* field means the strip became empty and should use the view's main input).
*/
function chipFieldLabel(spec) {
@@ -67,9 +69,114 @@ function chipLeadingSym(negated) {
);
}
function renderSignature(state, callbacks) {
const filters = state.filters || {};
const chipFields = state.chipFields || [];
const fields = [];
let visible = false;
for (let i = 0; i < chipFields.length; i++) {
const spec = chipFields[i];
const value = String(filters[spec.key] || '');
fields.push([
String(spec.key),
String(chipFieldLabel(spec)),
value,
log.isSubstringFilterField(spec.key)
]);
if (value && log.parseFilterValue(value).value) visible = true;
}
const labels = [
String(_('Exclude instead')),
String(_('Include instead')),
String(_('Remove filter')),
String(_('Clear all'))
];
return {
value: JSON.stringify([fields, labels]),
visible: visible,
onInvert: callbacks && callbacks.onInvert,
onClear: callbacks && callbacks.onClear,
onClearAll: callbacks && callbacks.onClearAll,
onFocusFallback: callbacks && callbacks.onFocusFallback
};
}
function signatureMatches(host, signature) {
const previous = host._fwliveChipRenderState;
return !!(
previous &&
previous.value === signature.value &&
previous.onInvert === signature.onInvert &&
previous.onClear === signature.onClear &&
previous.onClearAll === signature.onClearAll &&
previous.onFocusFallback === signature.onFocusFallback &&
host.className === 'fwlive-chips fwlive-chips-labels' &&
host.style &&
host.style.display === (signature.visible ? 'flex' : 'none')
);
}
function focusedChipControl(host) {
if (typeof document === 'undefined' || !document.activeElement) return null;
const active = document.activeElement;
let node = active;
while (node && node !== host) node = node.parentNode;
if (node !== host || typeof active.getAttribute !== 'function') return null;
const action = active.getAttribute('data-fwlive-chip-action');
if (!action) return null;
return {
action: action,
field: active.getAttribute('data-fwlive-chip-field')
};
}
function findChipControl(root, action, field) {
const pending = [root];
while (pending.length) {
const node = pending.pop();
if (!node || node.nodeType !== 1) continue;
if (
node.getAttribute &&
node.getAttribute('data-fwlive-chip-action') === action &&
(field == null || node.getAttribute('data-fwlive-chip-field') === field)
)
return node;
const children = node.childNodes || [];
for (let i = children.length - 1; i >= 0; i--) pending.push(children[i]);
}
return null;
}
function restoreChipFocus(host, focused, callbacks) {
if (!focused) return;
let target = findChipControl(host, focused.action, focused.field);
if (!target && focused.action !== 'clear-all')
target = findChipControl(host, 'clear-all', null);
if (target && typeof target.focus === 'function') {
target.focus();
return;
}
if (callbacks && typeof callbacks.onFocusFallback === 'function')
callbacks.onFocusFallback(
focused.action === 'clear-all' ? null : focused.field,
focused.action
);
}
function renderFilterChips(host, state, callbacks) {
const filters = state.filters || {};
const chipFields = state.chipFields || [];
const signature = renderSignature(state, callbacks);
if (signatureMatches(host, signature)) return;
const focused = focusedChipControl(host);
const chips = [];
for (let i = 0; i < chipFields.length; i++) {
@@ -99,6 +206,8 @@ function renderFilterChips(host, state, callbacks) {
{
'type': 'button',
'class': 'fwlive-chip-invert',
'data-fwlive-chip-field': String(spec.key),
'data-fwlive-chip-action': 'invert',
'aria-label': invertLabel,
'click': function (ev) {
callbacks.onInvert(spec.key, ev);
@@ -115,6 +224,9 @@ function renderFilterChips(host, state, callbacks) {
{
'href': '#',
'class': 'fwlive-chip-remove',
'data-fwlive-chip-field': String(spec.key),
'data-fwlive-chip-action': 'remove',
'aria-label': String(_('Remove filter')),
'title': _('Remove filter'),
'click': function (ev) {
callbacks.onClear(spec.key, ev);
@@ -140,6 +252,8 @@ function renderFilterChips(host, state, callbacks) {
host.innerHTML = '';
if (!chips.length) {
host.style.display = 'none';
host._fwliveChipRenderState = signature;
restoreChipFocus(host, focused, callbacks);
return;
}
@@ -152,6 +266,7 @@ function renderFilterChips(host, state, callbacks) {
{
'href': '#',
'class': 'fwlive-chip-clear',
'data-fwlive-chip-action': 'clear-all',
'click': function (ev) {
callbacks.onClearAll(ev);
}
@@ -159,6 +274,8 @@ function renderFilterChips(host, state, callbacks) {
[_('Clear all')]
)
);
host._fwliveChipRenderState = signature;
restoreChipFocus(host, focused, callbacks);
}
return baseclass.extend({
@@ -9,7 +9,7 @@
*/
return baseclass.extend({
/* Keep in sync with openwrt-feed/luci-app-fwlive/Makefile PKG_VERSION. */
APP_VERSION: '0.1.50',
APP_VERSION: '0.1.51',
ROW_LIMIT_OPTIONS: [25, 50, 100, 250, 500, 1000, 2000],
DEFAULT_ROW_LIMIT: 100,
FETCH_MODE_OPTIONS: ['auto', 'manual'],
@@ -11,5 +11,5 @@
* GENERATED — do not edit. Edit fwlive.css and run: node scripts/embed-fwlive-css.js
*/
return baseclass.extend({
styleText: "\n/* SPDX-License-Identifier: Apache-2.0 */\n.fwlive-map {\n\tmax-width: none;\n\twidth: 100%;\n\t--fwlive-pass-color: var(--success-color-high, var(--success-color, #46a546));\n\t--fwlive-deny-color: var(--error-color-high, var(--error-color, #ca3c3c));\n\t--fwlive-bg-medium: var(--background-color-medium, var(--white-color-low, #f9f9f9));\n\t--fwlive-paused-color: #b45309;\n}\n/* Wide page in Detail only — Simple keeps LuCI’s 940px column. */\n#maincontent.container:has(.fwlive-map[data-view=\"detailed\"]) {\n\tmax-width: none;\n\tpadding-left: 16px;\n\tpadding-right: 16px;\n}\n.fwlive-map[data-row-tint=\"accessible\"] {\n\t--fwlive-pass-color: #0d9488;\n\t--fwlive-deny-color: #c2410c;\n}\n.fwlive-map.fwlive-watch-paused .fwlive-title-row,\n.fwlive-map.fwlive-watch-paused .fwlive-watch-strip {\n\tbackground: rgba(180, 83, 9, 0.08);\n\tbackground: color-mix(in srgb, var(--fwlive-paused-color) 8%, transparent);\n}\n.fwlive-map.fwlive-watch-paused .fwlive-title-row {\n\tmargin: 0 -8px;\n\tpadding: 6px 8px 0;\n\tborder-radius: 3px 3px 0 0;\n}\n.fwlive-map.fwlive-watch-paused .fwlive-watch-strip {\n\tmargin: 0 -8px 10px;\n\tpadding: 8px;\n\tborder-radius: 0 0 3px 3px;\n\tborder-bottom-color: transparent;\n}\n.fwlive-title-row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tjustify-content: space-between;\n\tgap: 8px 16px;\n\tmargin: 0 0 4px;\n}\n.fwlive-title-row h2 {\n\tmargin: 0;\n\tflex: 0 1 auto;\n}\n.fwlive-title-status {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmin-width: 0;\n\tmargin-left: auto;\n}\n.fwlive-watch-strip {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: stretch;\n\tjustify-content: flex-start;\n\tgap: 0;\n\tmin-height: 40px;\n\tpadding: 8px 0 10px;\n\tborder-bottom: 1px solid var(--border-color-medium);\n\tmargin-bottom: 10px;\n}\n.fwlive-watch-group {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmin-height: 28px;\n\tpadding: 0 10px;\n\tborder-left: 1px solid var(--border-color-medium);\n\tflex-wrap: wrap;\n}\n.fwlive-watch-group:last-child {\n\tpadding-right: 0;\n}\n.fwlive-watch-group-label {\n\tmargin-right: 2px;\n\tcolor: var(--text-color-medium);\n\tfont-size: 0.72em;\n\tfont-weight: 700;\n\tletter-spacing: 0.08em;\n\ttext-transform: uppercase;\n\twhite-space: nowrap;\n}\n.fwlive-watch-seg {\n\tdisplay: inline-flex;\n\talign-items: stretch;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\toverflow: hidden;\n}\n.fwlive-watch-seg .fwlive-seg-btn {\n\tmargin: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tborder-right: 1px solid var(--border-color-medium);\n\tbackground: transparent;\n\tpadding: 4px 10px;\n\tfont-size: 0.88em;\n\tline-height: 1.25;\n\tmin-height: 28px;\n}\n.fwlive-watch-seg .fwlive-seg-btn:last-child {\n\tborder-right: 0;\n}\n.fwlive-watch-seg .fwlive-seg-btn[aria-pressed=\"true\"] {\n\tbackground: rgba(26, 111, 156, 0.12);\n\tbackground: color-mix(in srgb, var(--primary-color-high) 12%, transparent);\n\tcolor: var(--primary-color-high);\n\tfont-weight: 600;\n}\n.fwlive-watch-label { font-weight: 600; }\n.fwlive-watch-paused .fwlive-watch-label { color: var(--fwlive-paused-color); }\n.fwlive-map.fwlive-watch-paused .fwlive-title-status .fwlive-watch-label { color: var(--fwlive-paused-color); }\n.fwlive-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--text-color-low, var(--border-color-high));\n\tdisplay: inline-block;\n\tflex-shrink: 0;\n}\n.fwlive-dot.fwlive-dot-on {\n\tbackground: var(--fwlive-pass-color);\n\tbox-shadow: 0 0 0 3px color-mix(in srgb, var(--fwlive-pass-color) 22%, transparent);\n}\n.fwlive-btn-ghost {\n\tbackground: transparent;\n\tborder-color: var(--border-color-medium);\n}\n.fwlive-btn-quiet {\n\tbackground: transparent;\n\tborder-color: var(--border-color-medium);\n\tcolor: var(--text-color-medium);\n\tfont-weight: 500;\n}\n.fwlive-log-merged {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\tbackground: var(--background-color-medium, var(--white-color-low, var(--background-color-high)));\n\tcolor: var(--text-color-high);\n\tfont-weight: 500;\n\tfont-size: 0.88em;\n\tline-height: 1.25;\n\tmin-height: 28px;\n\tcursor: pointer;\n}\n.fwlive-log-merged:hover {\n\tbackground: var(--background-color-low, var(--background-color-medium));\n}\n.fwlive-log-merged:disabled {\n\topacity: 0.65;\n\tcursor: default;\n}\n.fwlive-log-on-dot {\n\twidth: 6px;\n\theight: 6px;\n\tborder-radius: 50%;\n\tbackground: var(--fwlive-pass-color);\n\tflex-shrink: 0;\n}\n.fwlive-log-rate {\n\tcolor: var(--text-color-medium);\n\tfont-weight: 400;\n}\n.fwlive-display-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px 14px;\n\tmargin: 0 0 12px;\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\tbackground: var(--background-color-high);\n}\n.fwlive-dLine truncated
styleText: "\n/* SPDX-License-Identifier: Apache-2.0 */\n.fwlive-map {\n\tmax-width: none;\n\twidth: 100%;\n\t--fwlive-pass-color: var(--success-color-high, var(--success-color, #46a546));\n\t--fwlive-deny-color: var(--error-color-high, var(--error-color, #ca3c3c));\n\t--fwlive-bg-medium: var(--background-color-medium, var(--white-color-low, #f9f9f9));\n\t--fwlive-paused-color: #b45309;\n}\n/* Wide page in Detail only — Simple keeps LuCI’s 940px column. */\n#maincontent.container:has(.fwlive-map[data-view=\"detailed\"]) {\n\tmax-width: none;\n\tpadding-left: 16px;\n\tpadding-right: 16px;\n}\n.fwlive-map[data-row-tint=\"accessible\"] {\n\t--fwlive-pass-color: #0d9488;\n\t--fwlive-deny-color: #c2410c;\n}\n.fwlive-map.fwlive-watch-paused .fwlive-title-row,\n.fwlive-map.fwlive-watch-paused .fwlive-watch-strip {\n\tbackground: rgba(180, 83, 9, 0.08);\n\tbackground: color-mix(in srgb, var(--fwlive-paused-color) 8%, transparent);\n}\n.fwlive-map.fwlive-watch-paused .fwlive-title-row {\n\tmargin: 0 -8px;\n\tpadding: 6px 8px 0;\n\tborder-radius: 3px 3px 0 0;\n}\n.fwlive-map.fwlive-watch-paused .fwlive-watch-strip {\n\tmargin: 0 -8px 10px;\n\tpadding: 8px;\n\tborder-radius: 0 0 3px 3px;\n\tborder-bottom-color: transparent;\n}\n.fwlive-title-row {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: baseline;\n\tjustify-content: space-between;\n\tgap: 8px 16px;\n\tmargin: 0 0 4px;\n}\n.fwlive-title-row h2 {\n\tmargin: 0;\n\tflex: 0 1 auto;\n}\n.fwlive-title-status {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 8px;\n\tflex-wrap: wrap;\n\tmin-width: 0;\n\tmargin-left: auto;\n}\n.fwlive-watch-strip {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: stretch;\n\tjustify-content: flex-start;\n\tgap: 0;\n\tmin-height: 40px;\n\tpadding: 8px 0 10px;\n\tborder-bottom: 1px solid var(--border-color-medium);\n\tmargin-bottom: 10px;\n}\n.fwlive-watch-group {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tmin-height: 28px;\n\tpadding: 0 10px;\n\tborder-left: 1px solid var(--border-color-medium);\n\tflex-wrap: wrap;\n}\n.fwlive-watch-group:last-child {\n\tpadding-right: 0;\n}\n.fwlive-watch-group-label {\n\tmargin-right: 2px;\n\tcolor: var(--text-color-medium);\n\tfont-size: 0.72em;\n\tfont-weight: 700;\n\tletter-spacing: 0.08em;\n\ttext-transform: uppercase;\n\twhite-space: nowrap;\n}\n.fwlive-watch-seg {\n\tdisplay: inline-flex;\n\talign-items: stretch;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\toverflow: hidden;\n}\n.fwlive-watch-seg .fwlive-seg-btn {\n\tmargin: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tborder-right: 1px solid var(--border-color-medium);\n\tbackground: transparent;\n\tpadding: 4px 10px;\n\tfont-size: 0.88em;\n\tline-height: 1.25;\n\tmin-height: 28px;\n}\n.fwlive-watch-seg .fwlive-seg-btn:last-child {\n\tborder-right: 0;\n}\n.fwlive-watch-seg .fwlive-seg-btn[aria-pressed=\"true\"] {\n\tbackground: rgba(26, 111, 156, 0.12);\n\tbackground: color-mix(in srgb, var(--primary-color-high) 12%, transparent);\n\tcolor: var(--primary-color-high);\n\tfont-weight: 600;\n}\n.fwlive-watch-label { font-weight: 600; }\n.fwlive-watch-paused .fwlive-watch-label { color: var(--fwlive-paused-color); }\n.fwlive-map.fwlive-watch-paused .fwlive-title-status .fwlive-watch-label { color: var(--fwlive-paused-color); }\n.fwlive-dot {\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--text-color-low, var(--border-color-high));\n\tdisplay: inline-block;\n\tflex-shrink: 0;\n}\n.fwlive-dot.fwlive-dot-on {\n\tbackground: var(--fwlive-pass-color);\n\tbox-shadow: 0 0 0 3px color-mix(in srgb, var(--fwlive-pass-color) 22%, transparent);\n}\n.fwlive-btn-ghost {\n\tbackground: transparent;\n\tborder-color: var(--border-color-medium);\n}\n.fwlive-btn-quiet {\n\tbackground: transparent;\n\tborder-color: var(--border-color-medium);\n\tcolor: var(--text-color-medium);\n\tfont-weight: 500;\n}\n.fwlive-log-merged {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 10px;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\tbackground: var(--background-color-medium, var(--white-color-low, var(--background-color-high)));\n\tcolor: var(--text-color-high);\n\tfont-weight: 500;\n\tfont-size: 0.88em;\n\tline-height: 1.25;\n\tmin-height: 28px;\n\tcursor: pointer;\n}\n.fwlive-log-merged:hover {\n\tbackground: var(--background-color-low, var(--background-color-medium));\n}\n.fwlive-log-merged:disabled {\n\topacity: 0.65;\n\tcursor: default;\n}\n.fwlive-log-on-dot {\n\twidth: 6px;\n\theight: 6px;\n\tborder-radius: 50%;\n\tbackground: var(--fwlive-pass-color);\n\tflex-shrink: 0;\n}\n.fwlive-log-rate {\n\tcolor: var(--text-color-medium);\n\tfont-weight: 400;\n}\n.fwlive-display-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px 14px;\n\tmargin: 0 0 12px;\n\tpadding: 8px 12px;\n\tborder: 1px solid var(--border-color-medium);\n\tborder-radius: 3px;\n\tbackground: var(--background-color-high);\n}\n.fwlive-dLine truncated
});
@@ -284,7 +284,8 @@
}
#fwlive-table { margin: 0; width: 100%; table-layout: auto; border-collapse: collapse; }
.fwlive-map[data-view="simple"] #fwlive-table { table-layout: fixed; width: 100%; }
.fwlive-map[data-view="simple"] col.fwlive-col-action { width: 5rem; }
/* Keep the localized action label and message button inside the first cell. */
.fwlive-map[data-view="simple"] col.fwlive-col-action { width: 11.5em; }
.fwlive-map[data-view="simple"] col.fwlive-col-time { width: 5rem; }
.fwlive-map[data-view="simple"] col.fwlive-col-iface { width: 5.5rem; }
.fwlive-map[data-view="simple"] col.fwlive-col-proto { width: 4rem; }
@@ -14,7 +14,8 @@ function createBatch() {
/* Own request serialization and scheduling. The runner owns result application
* and must check its epoch before applying asynchronous results. Adapters supply
* poll.add/remove, visibility.add/remove, isHidden, run, and initialCadence. */
* poll.add/remove, visibility.add/remove, isHidden, run, and initialCadence;
* optional options.onVisible is called on a hidden-to-visible transition. */
function createCoordinator(options) {
const { poll, visibility, isHidden, run, initialCadence } = options;
if (!Number.isFinite(initialCadence) || initialCadence <= 0)
@@ -180,7 +180,9 @@ function buildColumnCell(col, row, state, callbacks) {
case 'time': {
const timeAttrs = { 'class': columnCellClass(col) };
if (state.viewMode === 'simple')
timeAttrs.title = _('Click a row for the full message');
timeAttrs.title = _(
'Activate the message button or click a row to show or hide the full message'
);
return E('td', timeAttrs, [
state.viewMode === 'simple'
? log.formatTimestampCompact(row.timestamp)
@@ -219,6 +219,7 @@ return view.extend({
consentDismissedSession: false,
_loggingToolbarSig: '',
_loggingEmptySig: '',
_filterChipCallbacks: null,
tintFallbackActive: false,
tintProbeDone: false,
@@ -2140,9 +2141,42 @@ return view.extend({
this.onFilterInput();
},
focusFilterFallback(field) {
if (this.viewDisposed) return;
let target = field ? document.getElementById('fwlive-' + field) : null;
if (!target || typeof target.focus !== 'function')
target = document.getElementById('fwlive-q');
if (target && typeof target.focus === 'function') {
const details = typeof target.closest === 'function' ? target.closest('details') : null;
if (details && !details.open) details.open = true;
target.focus();
}
},
renderFilterChips() {
const bar = document.getElementById('fwlive-chips');
if (!bar) return;
if (!bar || this.viewDisposed) return;
if (!this._filterChipCallbacks) {
this._filterChipCallbacks = {
onInvert: (field, ev) => {
if (!this.viewDisposed) this.invertFilter(field, ev);
else {
if (ev && ev.preventDefault) ev.preventDefault();
if (ev && ev.stopPropagation) ev.stopPropagation();
}
},
onClear: (field, ev) => {
if (!this.viewDisposed) this.clearFilter(field, ev);
else if (ev && ev.preventDefault) ev.preventDefault();
},
onClearAll: (ev) => {
if (!this.viewDisposed) this.clearAllFilters(ev);
else if (ev && ev.preventDefault) ev.preventDefault();
},
onFocusFallback: (field) => this.focusFilterFallback(field)
};
}
chips.renderFilterChips(
bar,
@@ -2150,11 +2184,7 @@ return view.extend({
filters: Object.assign({}, this.readFilters()),
chipFields: this.FILTER_CHIP_FIELDS
},
{
onInvert: (field, ev) => this.invertFilter(field, ev),
onClear: (field, ev) => this.clearFilter(field, ev),
onClearAll: (ev) => this.clearAllFilters(ev)
}
this._filterChipCallbacks
);
},
@@ -2576,7 +2606,7 @@ return view.extend({
E('li', { 'id': 'fwlive-manual-test' }, []),
E('li', {}, [
_(
'In Simple view, activate the message button or click a row to show or hide the full log line.'
'In Simple view, activate the message button or click a row to show or hide the full message.'
)
]),
E('li', {}, [
@@ -2788,19 +2818,27 @@ return view.extend({
'class': 'cbi-input-text',
'placeholder': _('Quick search')
}),
E('select', { 'id': 'fwlive-action', 'class': 'cbi-input-select' }, [
E('option', { 'value': '' }, [_('Any action')]),
E('option', { 'value': 'pass' }, [_('pass')]),
E('option', { 'value': 'block' }, [_('block')]),
E('option', { 'value': 'drop' }, [_('drop')]),
E('option', { 'value': 'reject' }, [_('reject')]),
E('option', { 'value': 'unknown' }, [_('unknown')]),
E('option', { 'value': '!pass' }, [_('not pass')]),
E('option', { 'value': '!drop' }, [_('not drop')]),
E('option', { 'value': '!block' }, [_('not block')]),
E('option', { 'value': '!reject' }, [_('not reject')]),
E('option', { 'value': '!unknown' }, [_('not unknown')])
]),
E(
'select',
{
'id': 'fwlive-action',
'class': 'cbi-input-select',
'aria-label': String(_('Filter by %s').format(_('Action')))
},
[
E('option', { 'value': '' }, [_('Any action')]),
E('option', { 'value': 'pass' }, [_('pass')]),
E('option', { 'value': 'block' }, [_('block')]),
E('option', { 'value': 'drop' }, [_('drop')]),
E('option', { 'value': 'reject' }, [_('reject')]),
E('option', { 'value': 'unknown' }, [_('unknown')]),
E('option', { 'value': '!pass' }, [_('not pass')]),
E('option', { 'value': '!drop' }, [_('not drop')]),
E('option', { 'value': '!block' }, [_('not block')]),
E('option', { 'value': '!reject' }, [_('not reject')]),
E('option', { 'value': '!unknown' }, [_('not unknown')])
]
),
E('div', { 'class': 'fwlive-proto-pair' }, [
E(
'select',
@@ -2861,7 +2899,7 @@ return view.extend({
E('div', { 'id': 'fwlive-chips', 'class': 'fwlive-chips' }, []),
E('p', { 'class': 'fwlive-hint-line' }, [
_(
'Click a cell to filter · ≠ on a chip to exclude · Ctrl+click a rule for firewall settings · in Simple view, click a row for the full message'
'Click a cell to filter · ≠ on a chip to exclude · Ctrl+click a rule for firewall settings · in Simple view, activate the message button or click a row to show or hide the full message'
)
]),
E(