feat: Token 支持多项目关联且全部为 MCP 令牌;配置操作全量审计,支持按项目/键查询
build-and-deploy / build-and-deploy (push) Successful in 40s
build-and-deploy / build-and-deploy (push) Successful in 40s
This commit is contained in:
@@ -93,6 +93,11 @@ const API = {
|
||||
return this.request(`/api/projects/${projectId}/configs/${configId}`, { method: "DELETE" });
|
||||
},
|
||||
|
||||
getAudit(projectId, key) {
|
||||
const qs = key ? `?key=${encodeURIComponent(key)}` : "";
|
||||
return this.request(`/api/projects/${projectId}/configs/audit${qs}`);
|
||||
},
|
||||
|
||||
// ========== 模式 ==========
|
||||
|
||||
getMode() {
|
||||
|
||||
+102
-26
@@ -274,10 +274,11 @@ function renderConfigTable(configs, path) {
|
||||
<td><span class="type-badge">${escapeHtml(cfg.type)}</span></td>
|
||||
<td class="config-status-cell">${statusHtml.join("")}</td>
|
||||
<td>${escapeHtml(cfg.description)}</td>
|
||||
<td>
|
||||
<button class="row-btn view" data-action="view" data-id="${cfg.id}">查看</button>
|
||||
<button class="row-btn" data-action="edit" data-id="${cfg.id}">编辑</button>
|
||||
<button class="row-btn danger" data-action="delete" data-id="${cfg.id}">删除</button>
|
||||
<td>
|
||||
<button class="row-btn view" data-action="view" data-id="${cfg.id}">查看</button>
|
||||
<button class="row-btn" data-action="edit" data-id="${cfg.id}">编辑</button>
|
||||
<button class="row-btn" data-action="delete" data-id="${cfg.id}">删除</button>
|
||||
<button class="row-btn" data-action="audit" data-id="${cfg.id}">记录</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
@@ -670,9 +671,79 @@ async function copyAddress() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定所有事件监听
|
||||
* @returns {void}
|
||||
/**
|
||||
* 打开操作记录弹窗
|
||||
* @param {string|null} key - 为空时查当前项目全部记录;否则查指定配置键的记录
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function openAudit(key) {
|
||||
if (!state.currentProject) {
|
||||
toast("请先选择项目", "error");
|
||||
return;
|
||||
}
|
||||
document.getElementById("audit-title").textContent = key
|
||||
? `(${state.currentProject.name} · ${key})`
|
||||
: `(${state.currentProject.name})`;
|
||||
openModal("modal-audit");
|
||||
try {
|
||||
const logs = await API.getAudit(state.currentProject.id, key || "");
|
||||
renderAudit(logs);
|
||||
} catch (err) {
|
||||
toast("加载操作记录失败: " + err.message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染操作记录表格
|
||||
* @param {Array} logs - 操作记录列表
|
||||
* @returns {void}
|
||||
*/
|
||||
function renderAudit(logs) {
|
||||
const tbody = document.getElementById("audit-tbody");
|
||||
if (!logs || !logs.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:#999;padding:24px">暂无操作记录</td></tr>';
|
||||
return;
|
||||
}
|
||||
const ACTION_NAMES = { read: "读取", create: "新增", update: "更新", delete: "删除" };
|
||||
const SOURCE_NAMES = { web: "网页", mcp: "MCP" };
|
||||
tbody.innerHTML = logs
|
||||
.map((l) => {
|
||||
const act = ACTION_NAMES[l.action] || l.action;
|
||||
const src = SOURCE_NAMES[l.source] || l.source;
|
||||
const val = l.value !== undefined && l.value !== null && l.value !== "" ? String(l.value) : "—";
|
||||
return (
|
||||
"<tr>" +
|
||||
`<td>${escapeHtml(fmtTime(l.createdAt))}</td>` +
|
||||
`<td>${escapeHtml(src)}</td>` +
|
||||
`<td>${escapeHtml(l.actor || "")}</td>` +
|
||||
`<td>${l.ip ? escapeHtml(l.ip) : "—"}</td>` +
|
||||
`<td>${escapeHtml(act)}</td>` +
|
||||
`<td>${escapeHtml(l.key || "")}</td>` +
|
||||
`<td class="config-value-cell">${escapeHtml(val.length > 80 ? val.slice(0, 80) + "…" : val)}</td>` +
|
||||
"</tr>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间:YYYY-MM-DD HH:mm:ss
|
||||
* @param {string} iso - ISO 时间
|
||||
* @returns {string}
|
||||
*/
|
||||
function fmtTime(iso) {
|
||||
if (!iso) return "—";
|
||||
const d = new Date(iso);
|
||||
const pad = (n) => (n < 10 ? "0" + n : n);
|
||||
return (
|
||||
`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
|
||||
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定所有事件监听
|
||||
* @returns {void}
|
||||
*/
|
||||
function bindEvents() {
|
||||
// 项目选择
|
||||
@@ -784,17 +855,20 @@ function bindEvents() {
|
||||
if (e.key === "Enter") navigateToPath();
|
||||
});
|
||||
|
||||
// 导出/导入
|
||||
document.getElementById("btn-export-config").addEventListener("click", exportConfig);
|
||||
document.getElementById("btn-import-config").addEventListener("click", () => {
|
||||
document.getElementById("import-file-input").click();
|
||||
});
|
||||
document.getElementById("import-file-input").addEventListener("change", (e) => {
|
||||
if (e.target.files[0]) {
|
||||
importConfig(e.target.files[0]);
|
||||
e.target.value = "";
|
||||
}
|
||||
});
|
||||
// 导出/导入
|
||||
document.getElementById("btn-export-config").addEventListener("click", exportConfig);
|
||||
document.getElementById("btn-import-config").addEventListener("click", () => {
|
||||
document.getElementById("import-file-input").click();
|
||||
});
|
||||
document.getElementById("import-file-input").addEventListener("change", (e) => {
|
||||
if (e.target.files[0]) {
|
||||
importConfig(e.target.files[0]);
|
||||
e.target.value = "";
|
||||
}
|
||||
});
|
||||
|
||||
// 操作记录
|
||||
document.getElementById("btn-audit").addEventListener("click", () => openAudit(null));
|
||||
|
||||
// 表格操作(事件委托)
|
||||
document.getElementById("config-tbody").addEventListener("click", (e) => {
|
||||
@@ -805,14 +879,16 @@ function bindEvents() {
|
||||
const config = state.configs.find((c) => c.id === id);
|
||||
if (!config) return;
|
||||
|
||||
if (action === "view") {
|
||||
openConfigModal(config, true);
|
||||
} else if (action === "edit") {
|
||||
if (state.mode === "readonly") {
|
||||
toast("只读模式下不允许编辑", "error");
|
||||
return;
|
||||
}
|
||||
openConfigModal(config, false);
|
||||
if (action === "view") {
|
||||
openConfigModal(config, true);
|
||||
} else if (action === "edit") {
|
||||
if (state.mode === "readonly") {
|
||||
toast("只读模式下不允许编辑", "error");
|
||||
return;
|
||||
}
|
||||
openConfigModal(config, false);
|
||||
} else if (action === "audit") {
|
||||
openAudit(config.key);
|
||||
} else if (action === "delete") {
|
||||
if (state.mode === "readonly") {
|
||||
toast("只读模式下不允许删除", "error");
|
||||
|
||||
Reference in New Issue
Block a user