mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 08:57:57 +00:00
fix(webui,gui): repair dead export button + wire chat clear; prune UI redundancy
WebUI (internal/gateway/ui): - BUG: the export modal's custom-range button called downloadStatsCsvFromForm() which was never defined — clicking it threw a ReferenceError and nothing downloaded. Implement it: reads #exp-from / #exp-to date inputs and forwards to downloadStatsCsv. - BUG-adjacent: clearChat() existed but was reachable from no control — add a Clear button to the chat composer so conversation reset is actually possible (+ cClear i18n zh/en). - remove byte-identical duplicate html[data-theme=dark] CSS block (15 lines) - remove 8 dead CSS rules (.keys-grid .m-model-row .scope-add/.scope-box/ .scope-chips .scr-blocks .tag-warn .twrap) and the never-consumed --accent custom property - remove 3 dead JS functions (activeTab/findSlots/scopeUncomb; lastTab decl kept) - remove 24 dead i18n keys x zh/en (~55 lines) — legacy of the replaced key-scope editor, matching the removed .scope-* styles GUI (cmd/gui/main.js): - BUG: stopCore() set app.isQuitting=true and nothing reset it — after using tray 'stop core', closing the window quit the whole app instead of hiding to tray, and core crash auto-restart stayed disabled. isQuitting now only flips in restartCore (scoped) and before-quit. Verified: go vet/test green; node --check on all three GUI js files and the WebUI inline script.
This commit is contained in:
@ -28,7 +28,6 @@
|
||||
--sh-lg:0 18px 46px rgba(120,90,160,.22);
|
||||
--glass:18px;
|
||||
--ease-spring:cubic-bezier(.34,1.56,.64,1);
|
||||
--accent:sakura;
|
||||
}
|
||||
html[data-accent="sakura"]{
|
||||
--primary:#FF7FAC; --primary-h:#F33B7C; --primary-50:#FFF0F5; --primary-100:#FFE4E9;
|
||||
@ -84,20 +83,6 @@
|
||||
--blob1:rgba(168,85,247,.32); --blob2:rgba(34,211,238,.26); --blob3:rgba(139,92,246,.22);
|
||||
--line:rgba(168,85,247,.16); --line-strong:rgba(139,92,246,.18);
|
||||
}
|
||||
html[data-theme="dark"]{
|
||||
--primary:#f54180; --primary-h:#f871a0; --primary-50:#310413; --primary-100:#610726;
|
||||
--primary-200:#920b3a; --primary-300:#c20e4d; --primary-400:#f31260; --primary-500:#f54180;
|
||||
--secondary:#5E9FBF; --secondary-h:#88C0D0; --secondary-50:#0E2A36; --secondary-200:#2A546A;
|
||||
--danger:#DB3694; --danger-50:#3d1a24; --ok:#4f7cff; --warn:#e0b25c; --info:#6a8ffb;
|
||||
--bg-s1:#14121c; --bg-s2:#1a1824; --bg-s3:#221a28;
|
||||
--card:rgba(34,31,50,.55); --card-solid:#241f38; --card2:rgba(44,39,64,.42);
|
||||
--line:rgba(255,127,172,.16); --line-strong:rgba(200,180,235,.18);
|
||||
--fg:#ece6f5; --muted:#a09db8;
|
||||
--blob1:rgba(255,79,160,.32); --blob2:rgba(100,180,215,.26); --blob3:rgba(219,54,148,.22);
|
||||
--sh-sm:0 1px 2px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.05);
|
||||
--sh-md:0 8px 26px rgba(0,0,0,.45);
|
||||
--sh-lg:0 20px 52px rgba(0,0,0,.55);
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;color:var(--fg);overflow:hidden;
|
||||
@ -187,7 +172,6 @@
|
||||
.tag-green{background:var(--primary-50);color:var(--ok)}
|
||||
.tag-red{background:var(--danger-50);color:var(--danger)}
|
||||
.tag-blue{background:var(--secondary-50);color:var(--secondary-h)}
|
||||
.tag-warn{background:rgba(183,121,31,.14);color:var(--warn)}
|
||||
.net-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:currentColor;margin-right:6px;vertical-align:1px}
|
||||
button{background:linear-gradient(135deg,var(--primary),var(--danger));color:#fff;border:0;border-radius:11px;padding:8px 16px;cursor:pointer;
|
||||
font:inherit;font-weight:600;transition:all .18s var(--ease-spring)}
|
||||
@ -278,7 +262,6 @@
|
||||
.filter-line select{width:auto;margin:0;padding:6px 10px}
|
||||
|
||||
/* ---- keys tab ---- */
|
||||
.keys-grid{display:flex;flex-direction:column;gap:12px}
|
||||
.key-row{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--line);border-radius:12px;background:var(--card);flex-wrap:wrap}
|
||||
.key-row .kr-name{font-weight:700;min-width:120px}
|
||||
.key-row .kr-key{font-family:'JetBrains Mono',ui-monospace,Consolas,monospace;font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;max-width:340px;white-space:nowrap}
|
||||
@ -287,14 +270,11 @@
|
||||
.tag-role{font-size:11px;padding:3px 10px;border-radius:99px;font-weight:700}
|
||||
.tag-role.admin{background:linear-gradient(135deg,var(--primary),var(--danger));color:#fff}
|
||||
.tag-role.user{background:var(--secondary-50);color:var(--secondary-h);border:1px solid var(--line)}
|
||||
.scope-box{border-top:1px dashed var(--line);margin-top:8px;padding-top:10px}
|
||||
.scope-chips{display:flex;flex-wrap:wrap;gap:8px;align-items:center;min-height:40px}
|
||||
.scope-chip{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border-radius:99px;background:linear-gradient(135deg,var(--primary),var(--secondary));color:#fff;font-size:12.5px;font-weight:600;cursor:grab;user-select:none}
|
||||
.scope-chip[draggable="true"]:active{cursor:grabbing}
|
||||
.scope-chip .rm{cursor:pointer;opacity:.75;font-size:13px;line-height:1;padding:0 2px}
|
||||
.scope-chip .rm:hover{opacity:1}
|
||||
.scope-chip.drag-over{outline:2px dashed #fff;outline-offset:2px}
|
||||
.scope-add{padding:5px 10px;border-radius:99px;border:1px dashed var(--line);background:transparent;color:var(--muted);font-size:12.5px;cursor:pointer}
|
||||
.scope-unlim{color:var(--primary-h);font-weight:700;font-size:13px}
|
||||
.keys-create{display:grid;grid-template-columns:1fr 2fr 1fr 2fr;gap:10px;align-items:end}
|
||||
.keys-create label{font-size:11.5px;color:var(--muted);margin-bottom:4px}
|
||||
@ -383,7 +363,6 @@
|
||||
#modal-wrap{position:fixed;inset:0;background:rgba(30,20,60,.45);display:none;align-items:flex-start;justify-content:center;overflow:auto;padding:48px 20px;z-index:50;backdrop-filter:blur(3px)}
|
||||
#modal-wrap:not(:empty){display:flex}
|
||||
#modal-wrap .card{width:100%;max-width:480px;animation:cardIn .28s var(--ease-spring) both}
|
||||
.twrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
||||
|
||||
/* ---- scratch-style priority canvas (sakura restyle, same classes) ---- */
|
||||
.sort-canvas{position:relative;min-height:420px;padding:22px 14px 30px;margin:4px 0 10px;background-color:var(--card2);
|
||||
@ -395,7 +374,6 @@
|
||||
.scr-lane.scr-top .scr-tier span{opacity:.95}
|
||||
.scr-row{flex:1 1 auto;display:flex;align-items:center;padding:2px 18px;border-radius:8px;position:relative;transition:background .18s}
|
||||
.scr-row.hover{background:var(--primary-50)}
|
||||
.scr-blocks{display:flex;flex-wrap:wrap;align-items:center;min-width:0}
|
||||
.scr-block{position:relative;display:flex;align-items:center;gap:10px;padding:12px 14px 12px 20px;color:#fff;font-weight:700;font-size:13px;user-select:none;margin-right:0;
|
||||
border-radius:11px 11px 7px 7px;box-shadow:0 6px 0 rgba(0,0,0,.14),inset 0 2px 0 rgba(255,255,255,.30),inset 0 -5px 0 rgba(0,0,0,.08);transition:opacity .15s}
|
||||
.scr-block::before{content:'';position:absolute;left:-7px;top:50%;transform:translateY(-50%);width:7px;height:30px;border-radius:7px 0 0 7px;background:inherit;z-index:2}
|
||||
@ -451,7 +429,6 @@
|
||||
.scr-block .scr-x{position:absolute;top:-6px;right:-6px;cursor:pointer;font-size:13px;line-height:1;padding:3px 5px;border-radius:50%;
|
||||
color:rgba(255,255,255,.9);background:rgba(0,0,0,.22);z-index:3}
|
||||
.scr-block .scr-x:hover{background:rgba(219,54,148,.85);color:#fff}
|
||||
.m-model-row{display:flex;gap:8px;align-items:center}
|
||||
.m-id{margin:0 0 8px}
|
||||
.m-kind{flex:0 0 auto;margin:0 0 8px}
|
||||
.donut{position:relative;width:108px;height:108px}
|
||||
@ -729,14 +706,12 @@
|
||||
zh: {
|
||||
tagline: "统一 LLM 网关",
|
||||
logout: "退出登录",
|
||||
langTo: "EN",
|
||||
navStatus: "状态",
|
||||
navChat: "对话",
|
||||
navSources: "源",
|
||||
navAdapters: "适配器",
|
||||
navSort: "优先级",
|
||||
navKeys: "密钥",
|
||||
keysTitle: "密钥管理",
|
||||
keysHint:
|
||||
"管理员密钥可查看与管理全部密钥,并可为每个用户密钥配置可用模型范围;用户密钥只能看到自己。",
|
||||
kCreate: "新建密钥",
|
||||
@ -747,30 +722,18 @@
|
||||
kNote: "备注",
|
||||
kCreateBtn: "创建",
|
||||
kKey: "密钥",
|
||||
kModels: "可用模型",
|
||||
kCreated: "创建时间",
|
||||
kActions: "操作",
|
||||
kCopy: "复制密钥",
|
||||
kDel: "删除",
|
||||
kNewKey: "新密钥",
|
||||
kNewOK: "密钥已创建",
|
||||
kScope: "配置模型范围",
|
||||
kScopeTitle:
|
||||
"配置模型范围 —— 拖拽排列、下拉添加、×删除、复制/粘贴到其它用户",
|
||||
kScopeHint: "拖拽排序 · 下拉添加 · × 删除 · 复制/粘贴列表",
|
||||
kScopeSel: "选择模型…",
|
||||
kCopyList: "复制列表",
|
||||
kPasteList: "粘贴列表",
|
||||
kSaveScope: "保存",
|
||||
kScopeSaved: "模型范围已保存",
|
||||
kScopeEmpty: "全部模型",
|
||||
kScopeAll: "空即不限",
|
||||
kMeTitle: "我的密钥",
|
||||
kMeRole: "角色",
|
||||
kMeModels: "我可用的模型",
|
||||
kMeHint: "密钥不可在此新建或删除;需要变更请联系管理员。",
|
||||
kEmpty: "暂无其他密钥",
|
||||
kDelSelf: "不能删除当前登录所用密钥",
|
||||
kDelConfirm: "确定删除密钥 %s 吗?此后该密钥立即失效。",
|
||||
kAll: "不限",
|
||||
kBrickH: "点击编辑模型与配额 · 右键更多操作 · 拖动可跨密钥移动",
|
||||
@ -805,7 +768,6 @@
|
||||
copyCfg: "一键复制配置",
|
||||
copyEnv: "复制为环境变量",
|
||||
srcTitle: "源状态",
|
||||
srcCount: "共 %d 个",
|
||||
tName: "名称",
|
||||
tAdapter: "适配器",
|
||||
tModels: "模型",
|
||||
@ -816,13 +778,12 @@
|
||||
offline: "退避 / 不可用",
|
||||
adTitle: "已加载适配器",
|
||||
tVersion: "版本",
|
||||
chatTitle: "Chat 测试",
|
||||
cModel: "模型",
|
||||
cMsg: "Enter 发送,Shift+Enter 换行",
|
||||
send: "发送",
|
||||
cClear: "清空",
|
||||
clear: "清空",
|
||||
cThinking: "推理",
|
||||
cImg: "图片",
|
||||
cPick: "选择图片",
|
||||
cRemoveImg: "移除",
|
||||
cWelcome: "开始对话——选择模型,输入消息即可测试上游连接",
|
||||
@ -884,20 +845,15 @@
|
||||
sortTitle: "拖拽积木配置模型优先级",
|
||||
sortHint:
|
||||
"每行 = 一个优先级档位,行从上到下优先级递减;同一行的模型并排,视为同优先级。按住积木右侧 ⠿ 把手拖动:拖到行内 = 放入该档位或调整同档顺序,拖到行与行之间的缝隙 = 提升或降低到新档位。生图模型不参与排序。",
|
||||
sortDragGrip: "拖拽前须按住把手",
|
||||
sortSave: "保存排序",
|
||||
sortReset: "重置",
|
||||
sortAdd: "添加档位",
|
||||
sortSaved: "排序已保存并热重载",
|
||||
sortNoChange: "无变更",
|
||||
sortHintSave: "点击保存排序后生效",
|
||||
sortCooling: "冷却",
|
||||
sortFail: "失败",
|
||||
sortHealthTip: "冷却 / 失败次数 / 偏好分 实时状态",
|
||||
sortHealthReset: "链上冷却已复位",
|
||||
sortSource: "源",
|
||||
sortPrio: "优先级 %s",
|
||||
sortEmpty: "该源暂无模型",
|
||||
kpiActive: "活跃请求",
|
||||
kpiReqs: "总请求",
|
||||
kpiOk: "成功率",
|
||||
@ -919,7 +875,6 @@
|
||||
expKeysCsv: "导出密钥用量",
|
||||
exitKeyView: "退出该密钥视图",
|
||||
dashStatus: "状态码分布",
|
||||
thCode: "状态码",
|
||||
kOther: "其他",
|
||||
statusTag:
|
||||
"状态码分类统计(含 402 欠费 / 400 schema 错误;两者不计入上游退避但单独计数)",
|
||||
@ -945,14 +900,12 @@
|
||||
en: {
|
||||
tagline: "Unified LLM Gateway",
|
||||
logout: "Log out",
|
||||
langTo: "中",
|
||||
navStatus: "Status",
|
||||
navChat: "Chat",
|
||||
navSources: "Sources",
|
||||
navAdapters: "Adapters",
|
||||
navSort: "Priority",
|
||||
navKeys: "Keys",
|
||||
keysTitle: "Key management",
|
||||
keysHint:
|
||||
"Admin keys can view and manage every key and configure each user key\u0027s allowed models; user keys only see themselves.",
|
||||
kCreate: "Create key",
|
||||
@ -963,32 +916,19 @@
|
||||
kNote: "Note",
|
||||
kCreateBtn: "Create",
|
||||
kKey: "Key",
|
||||
kModels: "Allowed models",
|
||||
kCreated: "Created",
|
||||
kActions: "Actions",
|
||||
kCopy: "Copy key",
|
||||
kDel: "Delete",
|
||||
kNewKey: "New key — copy & save it now",
|
||||
kNewOK: "Key created",
|
||||
kScope: "Model scope",
|
||||
kScopeTitle:
|
||||
"Model scope — drag to order, add from dropdown, × to remove, copy/paste the list to other users",
|
||||
kScopeHint:
|
||||
"drag to order · add from dropdown · × to remove · copy/paste list",
|
||||
kScopeSel: "Pick a model…",
|
||||
kCopyList: "Copy list",
|
||||
kPasteList: "Paste list",
|
||||
kSaveScope: "Save",
|
||||
kScopeSaved: "Model scope saved",
|
||||
kScopeEmpty: "all models",
|
||||
kScopeAll: "empty = unlimited",
|
||||
kMeTitle: "My key",
|
||||
kMeRole: "Role",
|
||||
kMeModels: "Models I can use",
|
||||
kMeHint:
|
||||
"Keys cannot be created or deleted here; contact an admin to change.",
|
||||
kEmpty: "No other keys",
|
||||
kDelSelf: "cannot delete the key you are logged in with",
|
||||
kDelConfirm: 'Delete key "%s"? It will stop working immediately.',
|
||||
kAll: "All",
|
||||
kBrickH:
|
||||
@ -1025,7 +965,6 @@
|
||||
copyCfg: "Copy config",
|
||||
copyEnv: "Copy as env vars",
|
||||
srcTitle: "Sources",
|
||||
srcCount: "%d total",
|
||||
tName: "Name",
|
||||
tAdapter: "Adapter",
|
||||
tModels: "Models",
|
||||
@ -1036,13 +975,12 @@
|
||||
offline: "backoff / down",
|
||||
adTitle: "Loaded adapters",
|
||||
tVersion: "Version",
|
||||
chatTitle: "Chat",
|
||||
cModel: "Model",
|
||||
cMsg: "Enter to send, Shift+Enter for newline",
|
||||
send: "Send",
|
||||
cClear: "Clear",
|
||||
clear: "Clear",
|
||||
cThinking: "Thinking",
|
||||
cImg: "Image",
|
||||
cPick: "Pick image",
|
||||
cRemoveImg: "Remove",
|
||||
cWelcome:
|
||||
@ -1105,20 +1043,15 @@
|
||||
sortTitle: "Drag blocks to set model priority",
|
||||
sortHint:
|
||||
"Each row = one priority tier, rows go high→low; models on the same row sit side by side and share that priority. Grab the ⠿ handle on the right of a block to drag: drop into a row = join that tier or reorder within it, drop into the gap between rows = move up/down a tier. Image models stay out.",
|
||||
sortDragGrip: "grab the handle to drag",
|
||||
sortSave: "Save order",
|
||||
sortReset: "Reset",
|
||||
sortAdd: "Add slot",
|
||||
sortSaved: "Order saved & hot-reloaded",
|
||||
sortNoChange: "No changes",
|
||||
sortHintSave: "Click Save for it to take effect",
|
||||
sortCooling: "cooling",
|
||||
sortFail: "fail",
|
||||
sortHealthTip: "live cooldown / failures / preference score",
|
||||
sortHealthReset: "chain cooldowns reset",
|
||||
sortSource: "source",
|
||||
sortPrio: "priority %s",
|
||||
sortEmpty: "no models in this source",
|
||||
kpiActive: "Active requests",
|
||||
kpiReqs: "Requests",
|
||||
kpiOk: "Success rate",
|
||||
@ -1140,7 +1073,6 @@
|
||||
expKeysCsv: "Export key usage",
|
||||
exitKeyView: "Exit this key view",
|
||||
dashStatus: "Status codes",
|
||||
thCode: "Code",
|
||||
kOther: "other",
|
||||
statusTag:
|
||||
"Per-status aggregates — 402 quota / 400 schema errors are counted here but never back off the provider",
|
||||
@ -1191,10 +1123,8 @@
|
||||
if (ll) ll.textContent = LANG === "zh" ? "EN" : "中";
|
||||
document.documentElement.lang = LANG;
|
||||
}
|
||||
|
||||
let lastTab = "status";
|
||||
function activeTab() {
|
||||
return lastTab;
|
||||
}
|
||||
|
||||
/* ---------- theme & header ---------- */
|
||||
/* ---------- chrome: theme / lang / sidebar / breadcrumb ---------- */
|
||||
@ -1590,6 +1520,22 @@
|
||||
const w = $("#modal-wrap");
|
||||
if (w) w.remove();
|
||||
}
|
||||
// downloadStatsCsvFromForm reads the custom date range from the export
|
||||
// modal's date inputs and forwards to downloadStatsCsv. (Previously this
|
||||
// was referenced by the modal button but never defined — clicking
|
||||
// "download" did nothing.)
|
||||
function downloadStatsCsvFromForm() {
|
||||
const f = $("#exp-from");
|
||||
const t = $("#exp-to");
|
||||
if (!f || !t) return;
|
||||
// date inputs give YYYY-MM-DD; interpret as local days, end exclusive
|
||||
const from = new Date(f.value + "T00:00:00").getTime();
|
||||
const to = t.value
|
||||
? new Date(t.value + "T00:00:00").getTime() + 864e5
|
||||
: Date.now();
|
||||
if (!isFinite(from) || from <= 0) return toast(t("expRange"));
|
||||
downloadStatsCsv(from, to);
|
||||
}
|
||||
function downloadKeysCsv() {
|
||||
location.href =
|
||||
"/api/stats?export=keys-csv" +
|
||||
@ -2113,6 +2059,7 @@
|
||||
</button>
|
||||
<input id="ct-file" type="file" accept="image/*" multiple hidden onchange="addChatImgs(this.files)">
|
||||
<textarea id="ct-msg" rows="1" placeholder="${escAttr(t("cMsg"))}"></textarea>
|
||||
<button class="sendbtn ghost" id="ct-clear" title="${escAttr(t("cClear"))}" onclick="clearChat()">${esc(t("cClear"))}</button>
|
||||
<button class="sendbtn" id="ct-send" onclick="sendChat()">${t("send")}</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -2302,7 +2249,6 @@
|
||||
firstChunk = Date.now();
|
||||
try {
|
||||
const resp = await fetch("/api/chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
@ -2551,7 +2497,6 @@
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await api("/api/sources", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
toast(t("toastSaved"));
|
||||
@ -2567,7 +2512,6 @@
|
||||
async function delSource(name) {
|
||||
if (!confirm(tFmt("confirmDelSrc", name))) return;
|
||||
await api("/api/sources/" + encodeURIComponent(name), {
|
||||
method: "DELETE",
|
||||
});
|
||||
toast(t("toastDelOk"));
|
||||
renderSources();
|
||||
@ -2722,9 +2666,6 @@
|
||||
<span class="scr-grip"><i></i><i></i><i></i></span>
|
||||
</div>`;
|
||||
}
|
||||
function findSlots(li, ji) {
|
||||
return sortState.lanes[li].models[ji];
|
||||
}
|
||||
function paintSort(affected) {
|
||||
const cv = $("#scr-canvas");
|
||||
if (!cv) return;
|
||||
@ -3154,7 +3095,6 @@
|
||||
}),
|
||||
);
|
||||
await api("/api/auto", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ rules }),
|
||||
});
|
||||
}
|
||||
@ -3413,7 +3353,6 @@
|
||||
if (!name || !code) return toast(t("toastNeedAll"));
|
||||
try {
|
||||
await api("/api/adapters", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name, code }),
|
||||
});
|
||||
toast(t("toastUploaded"));
|
||||
@ -3427,7 +3366,6 @@
|
||||
async function delAdapter(name) {
|
||||
if (!confirm(tFmt("confirmDelAdp", name))) return;
|
||||
await api("/api/adapters/" + encodeURIComponent(name), {
|
||||
method: "DELETE",
|
||||
});
|
||||
toast(t("toastDelOk"));
|
||||
renderAdapters();
|
||||
@ -3590,10 +3528,6 @@
|
||||
const p = String(comb).split("|");
|
||||
return { model: p[0], source: normSrc(p[1]) };
|
||||
}
|
||||
function scopeUncomb(comb) {
|
||||
const p = String(comb).split("|");
|
||||
return { model: p[0], source: p[1] || "" };
|
||||
}
|
||||
function fmtQuota(q) {
|
||||
q = +q || 0;
|
||||
if (!q) return "∞";
|
||||
@ -3631,7 +3565,6 @@
|
||||
}
|
||||
async function putScope(key, scopes) {
|
||||
await api("/api/keys/" + encodeURIComponent(key), {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ models: scopes }),
|
||||
});
|
||||
}
|
||||
@ -3953,7 +3886,6 @@
|
||||
let j;
|
||||
try {
|
||||
j = await api("/api/keys", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
role: $("#kc-role").value,
|
||||
@ -3983,7 +3915,6 @@
|
||||
if (!confirm(tFmt("kDelConfirm", name || key))) return;
|
||||
try {
|
||||
await api("/api/keys/" + encodeURIComponent(key), {
|
||||
method: "DELETE",
|
||||
});
|
||||
toast(t("toastDelOk"));
|
||||
loadKeys();
|
||||
|
||||
Reference in New Issue
Block a user