feat(ui): dsh设置「用户管理」→「用户设置」;「平台管理」→「系统管理 + 每项弹窗管理」
用户要求(2026-09-13): - portal-entry:「用户管理」改名「用户设置」+ 去掉「打开管理台」入口(order 让位 102→103) - business-plugins:删掉跳转 portal 的按钮;分区改为管理项列表,5 项每项点开是原生弹窗; 「用户管理」弹窗内可直接 通过/禁用/启用/删除(调平台 admin API,不跳门户) - 版本:portal-entry 0.5.2 / business-plugins 0.3.0(均已铺发生效) - 验收脚本同步更新,npm run verify 全绿
This commit is contained in:
1 parent
5869a4a979
commit
281c0816fa
3 files changed
+200
-81
No files matched your search
@@ -163,16 +163,16 @@ window.__ModuleLoader__.load({
|
||||
"mem.safe": "余量充足",
|
||||
"mem.overWarn": "勾选后将超出单实例内存上限,实例可能起不来。建议先停用部分插件再应用。",
|
||||
"mem.perCard": "预估内存",
|
||||
"pa.label": "平台管理",
|
||||
"pa.label": "系统管理",
|
||||
"pa.readonly": "只读",
|
||||
"pa.loading": "加载中…",
|
||||
"pa.loadingDesc": "正在读取平台状态…",
|
||||
"pa.notAdminTitle": "平台管理仅对管理员显示",
|
||||
"pa.notAdminTitle": "系统管理仅对管理员显示",
|
||||
"pa.notAdminDesc": "当前账号不是管理员,此处不展示平台信息。",
|
||||
"pa.loadFailed": "平台状态读取失败",
|
||||
"pa.retry": "重试",
|
||||
"pa.openPortal": "打开完整管理台",
|
||||
"pa.readonlyHint": "本分区为只读;写操作请在平台管理台完成。",
|
||||
"pa.readonlyHint": "点开每一项,在弹窗内直接管理(无需跳转门户)。",
|
||||
"pa.inst": "当前实例",
|
||||
"pa.running": "运行中",
|
||||
"pa.stopped": "未运行",
|
||||
@@ -191,7 +191,24 @@ window.__ModuleLoader__.load({
|
||||
"pa.pool": "候选池",
|
||||
"pa.poolCount": "已投放插件",
|
||||
"pa.runtime": "运行时",
|
||||
"pa.items": "条目"
|
||||
"pa.items": "条目",
|
||||
"pa.usersTitle": "用户管理",
|
||||
"pa.colUser": "用户名",
|
||||
"pa.colRole": "角色",
|
||||
"pa.colOp": "操作",
|
||||
"pa.roleAdmin": "管理员",
|
||||
"pa.roleActive": "已启用",
|
||||
"pa.rolePending": "待审核",
|
||||
"pa.roleDisabled": "已禁用",
|
||||
"pa.opApprove": "通过",
|
||||
"pa.opDisable": "禁用",
|
||||
"pa.opEnable": "启用",
|
||||
"pa.opRemove": "删除",
|
||||
"pa.confirmRemove": "确认删除该用户?此操作不可撤销。",
|
||||
"pa.done": "已更新",
|
||||
"pa.failed": "操作失败",
|
||||
"pa.noUsers": "暂无用户",
|
||||
"pa.working": "处理中…"
|
||||
};
|
||||
/** English dictionary, key-set complete against zh. */
|
||||
var en = {
|
||||
@@ -238,16 +255,16 @@ window.__ModuleLoader__.load({
|
||||
"mem.safe": "headroom is fine",
|
||||
"mem.overWarn": "The selection exceeds the per-instance memory limit and the instance may fail to start. Disable some plugins first.",
|
||||
"mem.perCard": "est. memory",
|
||||
"pa.label": "Platform admin",
|
||||
"pa.label": "System management",
|
||||
"pa.readonly": "read-only",
|
||||
"pa.loading": "Loading…",
|
||||
"pa.loadingDesc": "Reading platform status…",
|
||||
"pa.notAdminTitle": "Platform admin is admin-only",
|
||||
"pa.notAdminTitle": "System management is admin-only",
|
||||
"pa.notAdminDesc": "This account is not an admin, so platform info is hidden here.",
|
||||
"pa.loadFailed": "Failed to read platform status",
|
||||
"pa.retry": "Retry",
|
||||
"pa.openPortal": "Open full admin console",
|
||||
"pa.readonlyHint": "This panel is read-only; do write actions in the admin console.",
|
||||
"pa.readonlyHint": "Open any item to manage it in a popup — no portal jump.",
|
||||
"pa.inst": "Current instance",
|
||||
"pa.running": "Running",
|
||||
"pa.stopped": "Stopped",
|
||||
@@ -266,7 +283,24 @@ window.__ModuleLoader__.load({
|
||||
"pa.pool": "Candidate pool",
|
||||
"pa.poolCount": "Published plugins",
|
||||
"pa.runtime": "Runtime",
|
||||
"pa.items": "items"
|
||||
"pa.items": "items",
|
||||
"pa.usersTitle": "User management",
|
||||
"pa.colUser": "Username",
|
||||
"pa.colRole": "Role",
|
||||
"pa.colOp": "Actions",
|
||||
"pa.roleAdmin": "Admin",
|
||||
"pa.roleActive": "Active",
|
||||
"pa.rolePending": "Pending",
|
||||
"pa.roleDisabled": "Disabled",
|
||||
"pa.opApprove": "Approve",
|
||||
"pa.opDisable": "Disable",
|
||||
"pa.opEnable": "Enable",
|
||||
"pa.opRemove": "Delete",
|
||||
"pa.confirmRemove": "Delete this user? This cannot be undone.",
|
||||
"pa.done": "Updated",
|
||||
"pa.failed": "Action failed",
|
||||
"pa.noUsers": "No users",
|
||||
"pa.working": "Working…"
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -972,29 +1006,39 @@ window.__ModuleLoader__.load({
|
||||
var st = useState({ loading: true, err: "", me: null, status: null, users: null, storage: null, pool: null, runtime: null });
|
||||
var s = st[0];
|
||||
var setS = st[1];
|
||||
var ul = useState([]);
|
||||
var userList = ul[0];
|
||||
var setUserList = ul[1];
|
||||
var mv = useState(null);
|
||||
var modal = mv[0];
|
||||
var setModal = mv[1];
|
||||
var bs = useState(false);
|
||||
var busy = bs[0];
|
||||
var setBusy = bs[1];
|
||||
var nt = useState("");
|
||||
var note = nt[0];
|
||||
var setNote = nt[1];
|
||||
var ne = useState(false);
|
||||
var noteErr = ne[0];
|
||||
var setNoteErr = ne[1];
|
||||
|
||||
function req(path, method) {
|
||||
return fetch(portalHost() + path, { credentials: "include", method: method || "GET" }).then(function (r) {
|
||||
return r.ok ? r.json().catch(function () { return {}; }) : null;
|
||||
}).catch(function () { return null; });
|
||||
}
|
||||
function loadUsers() { return req("/api/admin/users").then(function (d) { setUserList((d && d.users) || []); }); }
|
||||
function load() {
|
||||
setS(function (old) { return Object.assign({}, old, { loading: true, err: "" }); });
|
||||
var host = portalHost();
|
||||
function get(path) {
|
||||
return fetch(host + path, { credentials: "include" }).then(function (r) {
|
||||
return r.ok ? r.json() : null;
|
||||
}).catch(function () { return null; });
|
||||
}
|
||||
get("/api/auth/me").then(function (me) {
|
||||
req("/api/auth/me").then(function (me) {
|
||||
var role = me && me.user ? me.user.role : null;
|
||||
if (role !== "admin") {
|
||||
setS(function (old) { return Object.assign({}, old, { loading: false, me: me }); });
|
||||
return;
|
||||
}
|
||||
if (role !== "admin") { setS(function (old) { return Object.assign({}, old, { loading: false, me: me }); }); return; }
|
||||
Promise.all([
|
||||
get("/api/dsh/status"), get("/api/admin/users"),
|
||||
get("/api/admin/storage"), get("/api/plugins/business"), get("/api/admin/runtime")
|
||||
req("/api/dsh/status"), req("/api/admin/users"),
|
||||
req("/api/admin/storage"), req("/api/plugins/business"), req("/api/admin/runtime")
|
||||
]).then(function (r) {
|
||||
setS({
|
||||
loading: false, err: "", me: me,
|
||||
status: r[0], users: r[1], storage: r[2], pool: r[3], runtime: r[4]
|
||||
});
|
||||
setUserList((r[1] && r[1].users) || []);
|
||||
setS({ loading: false, err: "", me: me, status: r[0], users: r[1], storage: r[2], pool: r[3], runtime: r[4] });
|
||||
});
|
||||
}).catch(function () {
|
||||
setS(function (old) { return Object.assign({}, old, { loading: false, err: t("pa.loadFailed") }); });
|
||||
@@ -1002,65 +1046,140 @@ window.__ModuleLoader__.load({
|
||||
}
|
||||
useEffect(function () { load(); }, []);
|
||||
|
||||
var wrap = { padding: "4px 2px", fontSize: "13px", color: T.text, display: "flex", flexDirection: "column", gap: "12px" };
|
||||
if (s.loading) {
|
||||
return jsxRuntime.jsx("div", { style: wrap, children: [
|
||||
jsxRuntime.jsx("div", { key: "h", style: { fontSize: "15px", fontWeight: 500 }, children: t("pa.loading") }),
|
||||
jsxRuntime.jsx("div", { key: "d", style: { fontSize: "13px", color: T.sub }, children: t("pa.loadingDesc") })
|
||||
] });
|
||||
}
|
||||
if (s.err) {
|
||||
return jsxRuntime.jsx("div", { style: wrap, children: [
|
||||
jsxRuntime.jsx("div", { key: "h", style: { fontSize: "15px", fontWeight: 500, color: T.danger }, children: s.err }),
|
||||
jsxRuntime.jsx("button", {
|
||||
key: "b", type: "button", onClick: load,
|
||||
style: { alignSelf: "flex-start", padding: "6px 14px", borderRadius: "8px", cursor: "pointer", border: "1px solid " + T.border, background: "transparent", color: T.text, fontSize: "13px" },
|
||||
children: t("pa.retry")
|
||||
})
|
||||
] });
|
||||
}
|
||||
if (!s.me || !s.me.user || s.me.user.role !== "admin") {
|
||||
return jsxRuntime.jsx("div", { style: wrap, children: [
|
||||
jsxRuntime.jsx("div", { key: "h", style: { fontSize: "15px", fontWeight: 500 }, children: t("pa.notAdminTitle") }),
|
||||
jsxRuntime.jsx("div", { key: "d", style: { fontSize: "13px", color: T.sub, lineHeight: 1.6 }, children: t("pa.notAdminDesc") })
|
||||
] });
|
||||
// 管理动作:就地调用平台 admin API(不跳门户)
|
||||
function doUserAction(u, url, method, label) {
|
||||
if (method === "DELETE" && !window.confirm(t("pa.confirmRemove"))) return;
|
||||
setBusy(true); setNote("");
|
||||
req(url, method).then(function (r) {
|
||||
setBusy(false);
|
||||
setNoteErr(!r);
|
||||
setNote(r ? t("pa.done") : t("pa.failed"));
|
||||
return loadUsers();
|
||||
}).then(function () { return req("/api/admin/users"); }).then(function (d) { if (d && d.users) setUserList(d.users); });
|
||||
}
|
||||
|
||||
var wrap = { padding: "4px 2px", fontSize: "13px", color: T.text, display: "flex", flexDirection: "column", gap: "12px" };
|
||||
if (s.loading) return jsxRuntime.jsx("div", { style: wrap, children: [
|
||||
jsxRuntime.jsx("div", { key: "h", style: { fontSize: "15px", fontWeight: 500 }, children: t("pa.loading") }),
|
||||
jsxRuntime.jsx("div", { key: "d", style: { fontSize: "13px", color: T.sub }, children: t("pa.loadingDesc") }) ] });
|
||||
if (s.err) return jsxRuntime.jsx("div", { style: wrap, children: [
|
||||
jsxRuntime.jsx("div", { key: "h", style: { fontSize: "15px", fontWeight: 500, color: T.danger }, children: s.err }),
|
||||
jsxRuntime.jsx("button", { key: "b", type: "button", onClick: load, style: { alignSelf: "flex-start", padding: "6px 14px", borderRadius: "8px", cursor: "pointer", border: "1px solid " + T.border, background: "transparent", color: T.text, fontSize: "13px" }, children: t("pa.retry") }) ] });
|
||||
if (!s.me || !s.me.user || s.me.user.role !== "admin") return jsxRuntime.jsx("div", { style: wrap, children: [
|
||||
jsxRuntime.jsx("div", { key: "h", style: { fontSize: "15px", fontWeight: 500 }, children: t("pa.notAdminTitle") }),
|
||||
jsxRuntime.jsx("div", { key: "d", style: { fontSize: "13px", color: T.sub, lineHeight: 1.6 }, children: t("pa.notAdminDesc") }) ] });
|
||||
|
||||
var inst = s.status && s.status.running ? t("pa.running") : t("pa.stopped");
|
||||
var instDesc = s.status && s.status.instance
|
||||
? t("pa.port") + " " + s.status.instance.port + " · " + t("pa.restarts") + " " + (s.status.instance.restarts || 0)
|
||||
: "—";
|
||||
var brk = s.status && s.status.breaker;
|
||||
var users = (s.users && s.users.users) || [];
|
||||
var nTotal = users.length;
|
||||
var nPending = users.filter(function (u) { return u.role === "pending"; }).length;
|
||||
var nDisabled = users.filter(function (u) { return u.role === "disabled"; }).length;
|
||||
var nPending = userList.filter(function (u) { return u.role === "pending"; }).length;
|
||||
var nDisabled = userList.filter(function (u) { return u.role === "disabled"; }).length;
|
||||
var stUsers = (s.storage && s.storage.users) || [];
|
||||
var stDesc = s.storage && s.storage.generatedAt ? t("pa.storageNote") : (s.storage && s.storage.note) || "—";
|
||||
var pool = (s.pool && (s.pool.plugins || s.pool.items)) || [];
|
||||
var rt = (s.runtime && (s.runtime.items || s.runtime.groups)) || [];
|
||||
|
||||
return jsxRuntime.jsx("div", { style: wrap, children: [
|
||||
jsxRuntime.jsx("div", { key: "hd", style: { display: "flex", alignItems: "baseline", gap: "8px", flexWrap: "wrap" }, children: [
|
||||
jsxRuntime.jsx("span", { key: "a", style: { fontSize: "15px", fontWeight: 500 }, children: t("pa.label") }),
|
||||
jsxRuntime.jsx("span", { key: "b", style: { fontSize: "12px", color: T.dim }, children: t("pa.readonly") }),
|
||||
jsxRuntime.jsx("span", { key: "c", style: { fontSize: "12px", color: T.dim, flexBasis: "100%" }, children: t("pa.readonlyHint") })
|
||||
] }),
|
||||
jsxRuntime.jsx("div", { key: "cards", style: { display: "flex", gap: "10px", flexWrap: "wrap" }, children: [
|
||||
jsxRuntime.jsx(PaCard, { key: "i", title: t("pa.inst"), value: inst, color: s.status && s.status.running ? T.success : T.sub, desc: instDesc }),
|
||||
jsxRuntime.jsx(PaCard, { key: "b", title: t("pa.breaker"), value: brk ? t("pa.breakerOpen") : t("pa.breakerOk"), color: brk ? T.warn : T.success, desc: brk && brk.until ? String(brk.until) : "—" }),
|
||||
jsxRuntime.jsx(PaCard, { key: "u", title: t("pa.users"), value: nTotal, desc: t("pa.pending") + " " + nPending + " · " + t("pa.disabled") + " " + nDisabled }),
|
||||
jsxRuntime.jsx(PaCard, { key: "s", title: t("pa.storage"), value: stUsers.length, desc: stDesc }),
|
||||
jsxRuntime.jsx(PaCard, { key: "p", title: t("pa.pool"), value: pool.length, desc: t("pa.poolCount") }),
|
||||
jsxRuntime.jsx(PaCard, { key: "r", title: t("pa.runtime"), value: rt.length, desc: t("pa.items") })
|
||||
] }),
|
||||
jsxRuntime.jsx("button", {
|
||||
key: "open", type: "button",
|
||||
onClick: function () { window.open(portalHost() + "/portal.html", "_blank", "noopener"); },
|
||||
style: { alignSelf: "flex-start", padding: "7px 16px", borderRadius: "8px", cursor: "pointer", border: "1px solid " + T.primary, background: T.primary, color: "#ffffff", fontSize: "14px" },
|
||||
children: t("pa.openPortal")
|
||||
})
|
||||
] });
|
||||
// ── 管理项清单:每项点开都是「弹窗内管理」(用户 2026-09-13 要求)
|
||||
var ITEMS = [
|
||||
{ id: "users", title: t("pa.usersTitle"), desc: t("pa.pending") + " " + nPending + " · " + t("pa.disabled") + " " + nDisabled, badge: userList.length },
|
||||
{ id: "plugins", title: t("pa.pool"), desc: t("pa.poolCount"), badge: pool.length },
|
||||
{ id: "runtime", title: t("pa.runtime"), desc: t("pa.items"), badge: rt.length },
|
||||
{ id: "storage", title: t("pa.storage"), desc: t("pa.storageUsers"), badge: stUsers.length },
|
||||
{ id: "instance", title: t("pa.inst"), desc: inst + (brk ? " · " + t("pa.breakerOpen") : ""), badge: s.status && s.status.instance ? s.status.instance.port : "—" },
|
||||
];
|
||||
|
||||
function itemRow(it) {
|
||||
return jsxRuntime.jsx("div", {
|
||||
key: it.id, role: "button", tabIndex: 0,
|
||||
onClick: function () { setNote(""); setModal(it.id); },
|
||||
onKeyDown: function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setModal(it.id); } },
|
||||
style: { display: "flex", alignItems: "center", gap: "10px", padding: "10px 12px", border: "1px solid " + T.border, borderRadius: "10px", background: T.field, cursor: "pointer" },
|
||||
children: [
|
||||
jsxRuntime.jsx("div", { key: "t", style: { flex: "1 1 auto", minWidth: 0 }, children: [
|
||||
jsxRuntime.jsx("div", { key: "a", style: { fontSize: "14px", fontWeight: 500, color: T.text }, children: it.title }),
|
||||
jsxRuntime.jsx("div", { key: "b", style: { fontSize: "12px", color: T.dim, marginTop: "2px" }, children: it.desc }) ] }),
|
||||
jsxRuntime.jsx("div", { key: "n", style: { fontSize: "13px", color: T.sub, fontVariantNumeric: "tabular-nums" }, children: it.badge }),
|
||||
jsxRuntime.jsx("div", { key: "c", style: { color: T.dim, fontSize: "14px" }, children: "›" })
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function modalBody() {
|
||||
if (modal === "users") {
|
||||
return jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
|
||||
jsxRuntime.jsx("div", { key: "hd", style: { display: "flex", background: T.field, fontSize: "12px", color: T.dim, borderRadius: "8px 8px 0 0" }, children: [
|
||||
jsxRuntime.jsx("div", { key: "a", style: { flex: "1 1 auto", padding: "6px 10px" }, children: t("pa.colUser") }),
|
||||
jsxRuntime.jsx("div", { key: "b", style: { flex: "0 0 90px", padding: "6px 10px" }, children: t("pa.colRole") }),
|
||||
jsxRuntime.jsx("div", { key: "c", style: { flex: "0 0 190px", padding: "6px 10px" }, children: t("pa.colOp") }) ] }),
|
||||
(userList.length === 0
|
||||
? jsxRuntime.jsx("div", { key: "e", style: { padding: "12px", fontSize: "13px", color: T.dim }, children: t("pa.noUsers") })
|
||||
: userList.map(function (u) {
|
||||
var roleText = u.role === "admin" ? t("pa.roleAdmin") : u.role === "pending" ? t("pa.rolePending") : u.role === "disabled" ? t("pa.roleDisabled") : t("pa.roleActive");
|
||||
var ops = [];
|
||||
if (u.role === "pending") ops.push(["a", t("pa.opApprove"), "POST", "/api/admin/users/" + u.id + "/approve"]);
|
||||
if (u.role === "disabled") ops.push(["e", t("pa.opEnable"), "POST", "/api/admin/users/" + u.id + "/enable"]);
|
||||
if (u.role === "active") ops.push(["d", t("pa.opDisable"), "POST", "/api/admin/users/" + u.id + "/disable"]);
|
||||
ops.push(["r", t("pa.opRemove"), "DELETE", "/api/admin/users/" + u.id]);
|
||||
return jsxRuntime.jsx("div", { key: u.id, style: { display: "flex", alignItems: "center", borderTop: "1px solid " + T.border, fontSize: "13px", color: T.text }, children: [
|
||||
jsxRuntime.jsx("div", { key: "n", style: { flex: "1 1 auto", padding: "7px 10px", wordBreak: "break-all" }, children: u.username }),
|
||||
jsxRuntime.jsx("div", { key: "r", style: { flex: "0 0 90px", padding: "7px 10px", color: u.role === "pending" ? T.warn : u.role === "disabled" ? T.dim : T.sub }, children: roleText }),
|
||||
jsxRuntime.jsx("div", { key: "o", style: { flex: "0 0 190px", padding: "7px 10px", display: "flex", gap: "6px", flexWrap: "wrap" }, children: ops.map(function (o) {
|
||||
return jsxRuntime.jsx("button", { key: o[0], type: "button", disabled: busy, onClick: function () { doUserAction(u, o[3], o[2], o[1]); },
|
||||
style: { padding: "3px 10px", borderRadius: "6px", cursor: busy ? "default" : "pointer", fontSize: "12px", background: "transparent", border: "1px solid " + (o[0] === "r" ? T.danger : T.border), color: o[0] === "r" ? T.danger : T.text, opacity: busy ? 0.5 : 1 },
|
||||
children: o[1] });
|
||||
}) }),
|
||||
] });
|
||||
})),
|
||||
note ? jsxRuntime.jsx("div", { key: "n", style: { fontSize: "12px", color: noteErr ? T.danger : T.success }, children: note }) : null
|
||||
] });
|
||||
}
|
||||
if (modal === "plugins") {
|
||||
return jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children:
|
||||
pool.length === 0 ? jsxRuntime.jsx("div", { style: { fontSize: "13px", color: T.dim }, children: t("pa.empty") }) : pool.map(function (p, i) {
|
||||
return jsxRuntime.jsx("div", { key: (p.id || i), style: { padding: "8px 10px", border: "1px solid " + T.border, borderRadius: "8px", fontSize: "13px", color: T.text }, children: (p.name || p.id || "插件") + (p.version ? " v" + p.version : "") });
|
||||
}) });
|
||||
}
|
||||
if (modal === "runtime") {
|
||||
return jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: rt.map(function (x, i) {
|
||||
return jsxRuntime.jsx("div", { key: i, style: { padding: "8px 10px", border: "1px solid " + T.border, borderRadius: "8px", fontSize: "13px", color: T.text }, children: (x.name || x.title || "条目") + (x.version ? " " + x.version : "") });
|
||||
}) });
|
||||
}
|
||||
if (modal === "storage") {
|
||||
return jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: stUsers.length === 0
|
||||
? jsxRuntime.jsx("div", { style: { fontSize: "13px", color: T.dim }, children: (s.storage && s.storage.note) || "—" })
|
||||
: stUsers.map(function (u, i) { return jsxRuntime.jsx("div", { key: i, style: { padding: "8px 10px", border: "1px solid " + T.border, borderRadius: "8px", fontSize: "13px", color: T.text }, children: (u.username || u.id || "—") + (u.usedMb != null ? " " + u.usedMb + " MiB" : "") }); }) });
|
||||
}
|
||||
// instance
|
||||
var i2 = s.status || {};
|
||||
return jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px", fontSize: "13px", color: T.text }, children: [
|
||||
jsxRuntime.jsx("div", { key: "a", children: t("pa.inst") + ":" + inst }),
|
||||
jsxRuntime.jsx("div", { key: "b", children: t("pa.port") + ":" + ((i2.instance && i2.instance.port) || "—") }),
|
||||
jsxRuntime.jsx("div", { key: "c", children: t("pa.restarts") + ":" + ((i2.instance && i2.instance.restarts) || 0) }),
|
||||
jsxRuntime.jsx("div", { key: "d", children: t("pa.breaker") + ":" + (brk ? t("pa.breakerOpen") : t("pa.breakerOk")) }) ] });
|
||||
}
|
||||
|
||||
var rows = [
|
||||
jsxRuntime.jsx("div", { key: "hd", style: { fontSize: "15px", fontWeight: 500, color: T.text }, children: t("pa.label") }),
|
||||
jsxRuntime.jsx("div", { key: "sub", style: { fontSize: "12px", color: T.dim }, children: t("pa.readonlyHint") }),
|
||||
jsxRuntime.jsx("div", { key: "items", style: { display: "flex", flexDirection: "column", gap: "8px" }, children: ITEMS.map(itemRow) }),
|
||||
];
|
||||
if (modal) {
|
||||
var it = ITEMS.filter(function (x) { return x.id === modal; })[0] || { title: modal };
|
||||
rows.push(jsxRuntime.jsx("div", {
|
||||
key: "overlay",
|
||||
onClick: function () { setModal(null); },
|
||||
style: { position: "fixed", inset: 0, zIndex: 9999, background: "rgba(0,0,0,.4)", display: "flex", alignItems: "center", justifyContent: "center", padding: "24px" },
|
||||
children: jsxRuntime.jsx("div", {
|
||||
onClick: function (e) { e.stopPropagation(); },
|
||||
style: { background: T.text === "" ? "#fff" : ("var(--dsw-alias-bg-layer-1, #ffffff)"), border: "1px solid " + T.border, borderRadius: "12px", width: "100%", maxWidth: "760px", maxHeight: "78vh", overflow: "auto", padding: "16px", boxShadow: "0 8px 32px rgba(0,0,0,.2)" },
|
||||
children: [
|
||||
jsxRuntime.jsx("div", { key: "mh", style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "10px" }, children: [
|
||||
jsxRuntime.jsx("div", { key: "t", style: { fontSize: "15px", fontWeight: 500, color: T.text }, children: it.title }),
|
||||
jsxRuntime.jsx("button", { key: "x", type: "button", onClick: function () { setModal(null); }, style: { border: 0, background: "transparent", cursor: "pointer", fontSize: "16px", color: T.dim, padding: "2px 6px" }, children: "✕" }) ] }),
|
||||
modalBody()
|
||||
]
|
||||
})
|
||||
}));
|
||||
}
|
||||
return jsxRuntime.jsx("div", { style: wrap, children: rows });
|
||||
}
|
||||
|
||||
ctx.slots.inject("settings.section", function () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@dsh-local/business-plugins",
|
||||
"version": "0.2.9",
|
||||
"version": "0.3.0",
|
||||
"description": "功能管理(原「功能插件」)section for dsh web profile — v0.2.8(2026-09-13):**所有弹窗改页内弹窗** —— 弃用 window.confirm,改为 fixed 遮罩 + 居中面板 + 点遮罩/✕ 关闭(视觉与交互对齐 MCN 工作台 dsh-plugin-mcn 的 modalTitle/modalText/modalBtns 三段结构);**超限时弹窗内红底警告块**(标题 + 说明 + 内存数字),确认按钮转危险色,未点确认不发请求。v0.2.7(档案 75 维度 B 落地 · 2026-09-13):卡片新增「预估内存 ≈ N MiB」徽章(按实测成本分色:≥60 红 / ≥30 黄)、卡片加高(minHeight 112 + padding 14/16)便于四层信息排布;**列表上方新增「内存预估」状态条** —— 实心段=当前已启用插件预估占用、半透明段=本次勾选增量、超单实例上限(384 MiB)整条转红并给文字警告,确认弹窗也带上内存预估。口径 = 空载基线 285 MiB + 逐插件实测加载成本(隔离 cgroup 的 rss 增量),**是预估值不是实时读数**。候选池列表 + 搜索/状态徽章 + 批量启用/禁用 + 确认弹窗 + 重启后自动刷新。v0.2.4(档案 67):对齐 06-工作台UI规范 —— **信息层次反转**(主视觉改为插件用途说明,插件名/版本退为副行小字;中文说明由候选池入库时优先取官方目录)、字号阶梯 14/13/12、徽章与按钮改规范量级、空态改「标题 + 说明」、新增列表行 hover 反馈。v0.2.2:分区名由「功能插件」改为「功能管理」。v0.2.0:配色改用官方 --dsw-* design token(跟随 dsh 主题);文案走官方 locale(zh/en)。",
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
|
||||
@@ -104,12 +104,12 @@ ok("存在 platform-admin 分区", !!pa);
|
||||
if (pa) ok("其 order = 102", pa.meta.order === 102);
|
||||
ROLE = "admin"; delete globalThis.__OPENED;
|
||||
const tAdmin = text(await render(pa.Comp)).join(" | ");
|
||||
ok("admin:含分区标题「平台管理」", tAdmin.includes("平台管理"));
|
||||
ok("admin:6 张卡齐全", ["当前实例","熔断","用户","存储","候选池","运行时"].every(k => tAdmin.includes(k)));
|
||||
ok("admin:含只读声明", tAdmin.includes("只读"));
|
||||
ok("admin:含分区标题「系统管理」", tAdmin.includes("系统管理"));
|
||||
ok("admin:5 个管理项齐全", ["用户管理","候选池","运行时","存储","当前实例"].every(k => tAdmin.includes(k)));
|
||||
ok("admin:不再是只读(提示改为弹窗内管理)", tAdmin.includes("弹窗"));
|
||||
ROLE = "active";
|
||||
const tUser = text(await render(pa.Comp)).join(" | ");
|
||||
ok("非 admin:被门禁挡住", tUser.includes("仅对管理员显示"));
|
||||
ok("非 admin:不出现任何指标卡", !["候选池","运行时"].some(k => tUser.includes(k)));
|
||||
ok("非 admin:不出现任何管理项", !["候选池","运行时"].some(k => tUser.includes(k)));
|
||||
console.log(failed === 0 ? "\n结论:全绿 ✅" : "\n结论:有 " + failed + " 项失败 ❌");
|
||||
process.exit(failed === 0 ? 0 : 1);
|
||||
Reference in new issue
Block a user