fix(parental): исправление отображения HTML-тегов в таблице и кнопок действий

This commit is contained in:
Dushnilin
2026-08-17 20:38:16 +03:00
parent 1f116e571d
commit b435d965ed
2 changed files with 126 additions and 56 deletions
@@ -120,34 +120,40 @@ function createParentalContent(section) {
o.editable = true;
o.width = "5rem";
// Label / Rule name (Grid column + modal)
o = section.option(form.Value, "label", _("Rule Name"));
o.modalonly = false;
o.rmempty = false;
o.validate = function (sectionId, value) {
return value && `${value}`.trim().length > 0 ? true : _("Rule name is required");
};
o.placeholder = _("e.g. Kids Phone (Night YouTube)");
// Device column in table
o = section.option(form.DummyValue, "_device_display", _("Device / IP"));
o.modalonly = false;
o.rawhtml = true;
o.cfgvalue = function (sectionId) {
o.renderWidget = function (sectionId) {
const rawIp = uci.get(UCI_PACKAGE, sectionId, "device_ip");
const ips = Array.isArray(rawIp) ? rawIp : [rawIp].filter(Boolean);
if (ips.length === 0) return `<span style="opacity:0.5;">${_("All devices")}</span>`;
return ips.map((ip) => `<span class="badge" style="background:#2a3441;color:#63b3ed;padding:3px 8px;border-radius:6px;font-size:11px;font-family:monospace;margin-right:4px;font-weight:600;">${ip}</span>`).join(" ");
const ips = normalizeListValues(rawIp);
if (ips.length === 0) {
return E("span", { style: "opacity:0.5;" }, _("All devices"));
}
const badges = ips.map((ip) =>
E(
"span",
{
class: "badge",
style:
"background:#2a3441;color:#63b3ed;padding:3px 8px;border-radius:6px;font-size:11px;font-family:monospace;margin-right:4px;font-weight:600;display:inline-block;margin-bottom:2px;",
},
ip,
),
);
return E("div", {}, badges);
};
// Target column in table
o = section.option(form.DummyValue, "_target_display", _("Target"));
o.modalonly = false;
o.rawhtml = true;
o.cfgvalue = function (sectionId) {
o.renderWidget = function (sectionId) {
const target = uci.get(UCI_PACKAGE, sectionId, "target") || "all";
if (target === "all") {
return `<span style="color:#fc8181;font-weight:600;">🚫 ${_("All Internet")}</span>`;
return E(
"span",
{ style: "color:#fc8181;font-weight:600;" },
"🚫 " + _("All Internet"),
);
}
const rawSecs = uci.get(UCI_PACKAGE, sectionId, "sections");
const secNames = normalizeListValues(rawSecs);
@@ -155,59 +161,119 @@ function createParentalContent(section) {
secNames.push(target);
}
if (secNames.length === 0) {
return `<span style="opacity:0.6;">${_("No sections selected")}</span>`;
return E("span", { style: "opacity:0.6;" }, _("No sections selected"));
}
return secNames.map((name) => {
const badges = secNames.map((name) => {
const sec = uci.get(UCI_PACKAGE, name);
const label = (sec && (sec.label || sec[".name"])) || name;
return `<span class="badge" style="background:#2d3748;color:#f6ad55;padding:2px 6px;border-radius:4px;font-size:11px;margin-right:4px;border:1px solid rgba(246,173,85,0.3);">🔒 ${label}</span>`;
}).join(" ");
return E(
"span",
{
class: "badge",
style:
"background:#2d3748;color:#f6ad55;padding:2px 6px;border-radius:4px;font-size:11px;margin-right:4px;border:1px solid rgba(246,173,85,0.3);display:inline-block;margin-bottom:2px;",
},
"🔒 " + label,
);
});
return E("div", {}, badges);
};
// Action column in table
o = section.option(form.DummyValue, "_action_display", _("Mode"));
o.modalonly = false;
o.rawhtml = true;
o.cfgvalue = function (sectionId) {
o.renderWidget = function (sectionId) {
const action = uci.get(UCI_PACKAGE, sectionId, "action") || "block";
if (action === "allow") {
return `<span style="color:#68d391;font-weight:500;">✅ ${_("Allow in interval")}</span>`;
return E(
"span",
{ style: "color:#68d391;font-weight:500;" },
"✅ " + _("Allow in interval"),
);
}
return `<span style="color:#fc8181;font-weight:500;">🚫 ${_("Block in interval")}</span>`;
return E(
"span",
{ style: "color:#fc8181;font-weight:500;" },
"🚫 " + _("Block in interval"),
);
};
// Schedule column in table
o = section.option(form.DummyValue, "_schedule_display", _("Schedule"));
o.modalonly = false;
o.rawhtml = true;
o.cfgvalue = function (sectionId) {
o.renderWidget = function (sectionId) {
const s = uci.get(UCI_PACKAGE, sectionId, "start_time") || "00:00";
const e = uci.get(UCI_PACKAGE, sectionId, "end_time") || "23:59";
const days = formatDaysDisplay(uci.get(UCI_PACKAGE, sectionId, "days"));
return `<div style="line-height:1.3;"><strong style="color:var(--text-color, #fff);">${s} — ${e}</strong><br><small style="opacity:0.7;">${days}</small></div>`;
return E("div", { style: "line-height:1.3;" }, [
E("strong", { style: "color:var(--text-color, #fff);" }, `${s} — ${e}`),
E("br"),
E("small", { style: "opacity:0.7;" }, days),
]);
};
// Live status badge in table
o = section.option(form.DummyValue, "_live_status", _("Status"));
o.modalonly = false;
o.rawhtml = true;
o.cfgvalue = function (sectionId) {
o.renderWidget = function (sectionId) {
const enabled = uci.get(UCI_PACKAGE, sectionId, "enabled") !== "0";
if (!enabled) {
return `<span style="display:inline-flex;align-items:center;padding:2px 8px;border-radius:12px;font-size:11px;background:#4a5568;color:#e2e8f0;">⏸ ${_("Disabled")}</span>`;
return E(
"span",
{
style:
"display:inline-flex;align-items:center;padding:2px 8px;border-radius:12px;font-size:11px;background:#4a5568;color:#e2e8f0;",
},
"⏸ " + _("Disabled"),
);
}
const active = isScheduleCurrentlyActive(sectionId);
const action = uci.get(UCI_PACKAGE, sectionId, "action") || "block";
if (active) {
const color = action === "allow" ? "#48bb78" : "#e53e3e";
const text = action === "allow" ? _("Active (Allowed)") : _("Active (Blocking)");
return `<span style="display:inline-flex;align-items:center;padding:2px 8px;border-radius:12px;font-size:11px;background:rgba(${action === "allow" ? "72,187,120" : "229,62,62"},0.2);color:${color};border:1px solid rgba(${action === "allow" ? "72,187,120" : "229,62,62"},0.4);"><span style="width:6px;height:6px;border-radius:50%;background:${color};margin-right:5px;box-shadow:0 0 6px ${color};"></span>${text}</span>`;
const bg =
action === "allow" ? "rgba(72,187,120,0.2)" : "rgba(229,62,62,0.2)";
const border =
action === "allow" ? "rgba(72,187,120,0.4)" : "rgba(229,62,62,0.4)";
const text =
action === "allow" ? _("Active (Allowed)") : _("Active (Blocking)");
return E(
"span",
{
style: `display:inline-flex;align-items:center;padding:2px 8px;border-radius:12px;font-size:11px;background:${bg};color:${color};border:1px solid ${border};`,
},
[
E("span", {
style: `width:6px;height:6px;border-radius:50%;background:${color};margin-right:5px;box-shadow:0 0 6px ${color};`,
}),
text,
],
);
}
return `<span style="display:inline-flex;align-items:center;padding:2px 8px;border-radius:12px;font-size:11px;background:rgba(74,85,104,0.3);color:#a0aec0;border:1px solid rgba(74,85,104,0.3);"><span style="width:6px;height:6px;border-radius:50%;background:#718096;margin-right:5px;"></span>${_("Pending")}</span>`;
return E(
"span",
{
style:
"display:inline-flex;align-items:center;padding:2px 8px;border-radius:12px;font-size:11px;background:rgba(74,85,104,0.3);color:#a0aec0;border:1px solid rgba(74,85,104,0.3);",
},
[
E("span", {
style:
"width:6px;height:6px;border-radius:50%;background:#718096;margin-right:5px;",
}),
_("Pending"),
],
);
};
// ─── Modal only configuration options ──────────────────────────────────────
// Label / Rule name (Modal only)
o = section.option(form.Value, "label", _("Rule Name"));
o.modalonly = true;
o.rmempty = true;
o.placeholder = _("e.g. Kids Phone (Night YouTube)");
// Device selector with LAN hostnames integration
o = section.option(
form.DynamicList,
@@ -88,28 +88,32 @@ function configureGridSection(sectionRef, type, title, addTitle) {
return renderSectionAdd(sectionRef, extra_class);
};
if (type === "section") {
sectionRef.renderRowActions = function (section_id) {
const els = form.TableSection.prototype.renderRowActions.call(
this,
section_id,
getRuleEditButtonText(),
sectionRef.renderRowActions = function (section_id) {
const els = form.TableSection.prototype.renderRowActions.call(
this,
section_id,
getRuleEditButtonText(),
);
if (type === "section") {
const btn = E(
"button",
{
type: "button",
class: "btn cbi-button cbi-button-action",
title: _("Rules"),
click: function (ev) {
ev.preventDefault();
ev.stopPropagation();
section.showSectionRulesModal(section_id);
},
},
_("Rules"),
);
const btn = E("button", {
type: "button",
class: "btn cbi-button cbi-button-action",
title: _("Rules"),
click: function (ev) {
ev.preventDefault();
ev.stopPropagation();
section.showSectionRulesModal(section_id);
}
}, _("Rules"));
if (els && els.lastElementChild) {
const div = els.lastElementChild;
const editBtn = div.querySelector('.cbi-button-edit');
const editBtn = div.querySelector(".cbi-button-edit");
if (editBtn) {
div.insertBefore(btn, editBtn);
btn.style.marginRight = "5px";
@@ -117,10 +121,10 @@ function configureGridSection(sectionRef, type, title, addTitle) {
div.appendChild(btn);
}
}
}
return els;
};
}
return els;
};
}
const EntryPoint = {